@marktiderman/genesis-chat 0.1.0 → 0.2.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 (162) hide show
  1. package/README.md +304 -13
  2. package/dist/{chunk-SAY24E6V.js → chunk-5D5A7I4Q.js} +166 -45
  3. package/dist/chunk-5D5A7I4Q.js.map +1 -0
  4. package/dist/{chunk-JQXZUEEL.js → chunk-7S6HYNWW.js} +382 -8
  5. package/dist/chunk-7S6HYNWW.js.map +1 -0
  6. package/dist/chunk-BJCE5O7V.js +70 -0
  7. package/dist/chunk-BJCE5O7V.js.map +1 -0
  8. package/dist/chunk-PZTOY2LY.js +576 -0
  9. package/dist/chunk-PZTOY2LY.js.map +1 -0
  10. package/dist/chunk-VEPZROQB.js +187 -0
  11. package/dist/chunk-VEPZROQB.js.map +1 -0
  12. package/dist/{client-BtdALFCx.d.ts → client-DMa2j7kz.d.ts} +152 -11
  13. package/dist/index.d.ts +78 -163
  14. package/dist/index.js +3 -2
  15. package/dist/native.d.ts +1910 -1
  16. package/dist/native.js +4900 -0
  17. package/dist/native.js.map +1 -1
  18. package/dist/outbox-B0y3phE0.d.ts +151 -0
  19. package/dist/push.d.ts +206 -0
  20. package/dist/push.js +3 -0
  21. package/dist/push.js.map +1 -0
  22. package/dist/react.d.ts +149 -7
  23. package/dist/react.js +3 -3
  24. package/dist/server.d.ts +510 -21
  25. package/dist/server.js +863 -21
  26. package/dist/server.js.map +1 -1
  27. package/dist/thread-DWXHKPdw.d.ts +57 -0
  28. package/dist/web.d.ts +313 -10
  29. package/dist/web.js +1731 -60
  30. package/dist/web.js.map +1 -1
  31. package/migrations/0002_genesis_chat_coaching.sql +934 -0
  32. package/migrations/tests/0002_genesis_chat_coaching.test.sql +265 -0
  33. package/package.json +74 -6
  34. package/src/__tests__/coaching-core.test.ts +332 -0
  35. package/src/__tests__/coaching-react.test.tsx +248 -0
  36. package/src/__tests__/coaching-web.test.tsx +449 -0
  37. package/src/__tests__/core.test.ts +91 -0
  38. package/src/__tests__/outbox.test.ts +122 -0
  39. package/src/__tests__/realtime.test.ts +70 -0
  40. package/src/__tests__/review-fixes-core.test.ts +193 -0
  41. package/src/__tests__/review-fixes-web.test.tsx +402 -0
  42. package/src/__tests__/server-effects.test.ts +158 -0
  43. package/src/__tests__/server-fakes.ts +90 -0
  44. package/src/__tests__/server-integrations.test.ts +247 -0
  45. package/src/__tests__/server-providers.test.ts +246 -0
  46. package/src/__tests__/server-runner.test.ts +277 -0
  47. package/src/__tests__/server-safety.test.ts +290 -0
  48. package/src/__tests__/web-markdown.test.tsx +88 -0
  49. package/src/__tests__/web.test.tsx +333 -0
  50. package/src/core/availability.ts +223 -0
  51. package/src/core/client.ts +757 -0
  52. package/src/core/extras.ts +106 -0
  53. package/src/core/media.ts +144 -0
  54. package/src/core/outbox.ts +429 -0
  55. package/src/core/thread.ts +171 -0
  56. package/src/core/types.ts +311 -0
  57. package/src/index.ts +13 -0
  58. package/src/native/__tests__/auto-retry.test.tsx +128 -0
  59. package/src/native/__tests__/composer-mic-lock.test.tsx +115 -0
  60. package/src/native/__tests__/composer-state.test.ts +188 -0
  61. package/src/native/__tests__/composer.test.tsx +133 -0
  62. package/src/native/__tests__/emoji.test.tsx +238 -0
  63. package/src/native/__tests__/inbox-screen.test.tsx +48 -0
  64. package/src/native/__tests__/message-bubble.test.tsx +127 -0
  65. package/src/native/__tests__/organise-logic.test.ts +164 -0
  66. package/src/native/__tests__/organise-screens.test.tsx +344 -0
  67. package/src/native/__tests__/photo-viewer.test.tsx +226 -0
  68. package/src/native/__tests__/push-payload.test.ts +66 -0
  69. package/src/native/__tests__/push.test.ts +209 -0
  70. package/src/native/__tests__/recorder-machine.test.ts +96 -0
  71. package/src/native/__tests__/rows-actions.test.ts +76 -0
  72. package/src/native/__tests__/thread-layout.test.ts +41 -0
  73. package/src/native/__tests__/thread-screen.test.tsx +116 -0
  74. package/src/native/__tests__/tus.test.ts +154 -0
  75. package/src/native/__tests__/use-voice-recorder.test.tsx +316 -0
  76. package/src/native/__tests__/video-media.test.tsx +421 -0
  77. package/src/native/__tests__/video-recorder-state.test.ts +87 -0
  78. package/src/native/__tests__/video-upload.test.ts +227 -0
  79. package/src/native/access-error.tsx +31 -0
  80. package/src/native/actions.ts +42 -0
  81. package/src/native/attachment-cards.tsx +116 -0
  82. package/src/native/auto-retry.ts +69 -0
  83. package/src/native/availability.tsx +376 -0
  84. package/src/native/broadcast-sheet.tsx +193 -0
  85. package/src/native/composer-state.ts +302 -0
  86. package/src/native/composer.tsx +453 -0
  87. package/src/native/context.tsx +70 -0
  88. package/src/native/copy.ts +296 -0
  89. package/src/native/emoji-data.ts +21 -0
  90. package/src/native/emoji-picker.tsx +229 -0
  91. package/src/native/emoji.ts +118 -0
  92. package/src/native/icon-button.tsx +40 -0
  93. package/src/native/inbox-screen.tsx +116 -0
  94. package/src/native/index.ts +184 -0
  95. package/src/native/link-preview.tsx +104 -0
  96. package/src/native/media-colors.ts +15 -0
  97. package/src/native/media.tsx +187 -0
  98. package/src/native/message-actions-sheet.tsx +121 -0
  99. package/src/native/message-bubble.tsx +374 -0
  100. package/src/native/moderation.tsx +42 -0
  101. package/src/native/modules.ts +275 -0
  102. package/src/native/organise-copy.ts +195 -0
  103. package/src/native/photo-viewer.tsx +531 -0
  104. package/src/native/push.ts +375 -0
  105. package/src/native/recorder-machine.ts +176 -0
  106. package/src/native/rows.ts +81 -0
  107. package/src/native/schedule-sheet.tsx +314 -0
  108. package/src/native/search-screen.tsx +168 -0
  109. package/src/native/stack-options.ts +36 -0
  110. package/src/native/start-sheet.tsx +87 -0
  111. package/src/native/thread-screen.tsx +620 -0
  112. package/src/native/topics.tsx +179 -0
  113. package/src/native/tus.ts +282 -0
  114. package/src/native/use-composer.ts +71 -0
  115. package/src/native/use-jump.ts +91 -0
  116. package/src/native/use-voice-recorder.ts +234 -0
  117. package/src/native/video-player.tsx +370 -0
  118. package/src/native/video-recorder-state.ts +140 -0
  119. package/src/native/video-recorder.tsx +271 -0
  120. package/src/native/video-upload-tray.tsx +77 -0
  121. package/src/native/video-upload.ts +432 -0
  122. package/src/native/voice-player.tsx +283 -0
  123. package/src/react/index.tsx +810 -0
  124. package/src/server/assistant.ts +235 -0
  125. package/src/server/crypto.ts +86 -0
  126. package/src/server/expo.ts +119 -0
  127. package/src/server/handler.ts +32 -0
  128. package/src/server/index.ts +57 -0
  129. package/src/server/moderation.ts +155 -0
  130. package/src/server/mux.ts +290 -0
  131. package/src/server/notifications.ts +130 -0
  132. package/src/server/runner.ts +468 -0
  133. package/src/server/transcribers.ts +111 -0
  134. package/src/server/types.ts +171 -0
  135. package/src/server/unfurl.ts +480 -0
  136. package/src/server/upload.ts +158 -0
  137. package/src/web/attachments.tsx +379 -0
  138. package/src/web/availability.tsx +299 -0
  139. package/src/web/broadcast.tsx +166 -0
  140. package/src/web/bubble.tsx +563 -0
  141. package/src/web/cards.tsx +136 -0
  142. package/src/web/composer.tsx +571 -0
  143. package/src/web/emoji-picker.tsx +84 -0
  144. package/src/web/error-boundary.tsx +33 -0
  145. package/src/web/format.ts +55 -0
  146. package/src/web/inbox.tsx +195 -0
  147. package/src/web/index.ts +75 -0
  148. package/src/web/markdown.tsx +245 -0
  149. package/src/web/memory-client.ts +706 -0
  150. package/src/web/panel.tsx +135 -0
  151. package/src/web/schedule.tsx +192 -0
  152. package/src/web/search.tsx +129 -0
  153. package/src/web/thread-context.ts +64 -0
  154. package/src/web/thread.tsx +802 -0
  155. package/src/web/topics.tsx +160 -0
  156. package/src/web/use-voice-recorder.ts +264 -0
  157. package/src/web/video.tsx +522 -0
  158. package/src/web/voice-note.tsx +197 -0
  159. package/dist/chunk-27UTR5FW.js +0 -322
  160. package/dist/chunk-27UTR5FW.js.map +0 -1
  161. package/dist/chunk-JQXZUEEL.js.map +0 -1
  162. package/dist/chunk-SAY24E6V.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/index.tsx"],"names":["list"],"mappings":";;;;;;AA6BO,IAAM,QAAA,GAAW;AAAA,EACtB,GAAA,EAAK,CAAC,cAAc,CAAA;AAAA,EACpB,KAAA,EAAO,CAAC,IAAA,KAAkB,CAAC,gBAAgB,OAAA,EAAS,IAAA,IAAQ,EAAE,CAAA;AAAA,EAC9D,MAAA,EAAQ,MAAM,CAAC,cAAA,EAAgB,QAAQ,CAAA;AAAA,EACvC,cAAc,CAAC,EAAA,KAAe,CAAC,cAAA,EAAgB,gBAAgB,EAAE,CAAA;AAAA,EACjE,SAAS,CAAC,EAAA,KAAe,CAAC,cAAA,EAAgB,WAAW,EAAE,CAAA;AAAA,EACvD,MAAM,CAAC,EAAA,KAAe,CAAC,cAAA,EAAgB,QAAQ,EAAE,CAAA;AAAA,EACjD,MAAM,CAAC,EAAA,KAAe,CAAC,cAAA,EAAgB,QAAQ,EAAE,CAAA;AAAA,EACjD,KAAA,EAAO,CAAC,KAAA,KAAoB,CAAC,cAAA,EAAgB,OAAA,EAAS,CAAC,GAAG,KAAK,CAAA,CAAE,IAAA,EAAM,CAAA;AAAA;AAAA,EAEvE,QAAQ,CAAC,EAAA,KAAe,CAAC,cAAA,EAAgB,cAAA,EAAgB,IAAI,QAAQ,CAAA;AAAA,EACrE,WAAW,CAAC,cAAA,KAAmC,CAAC,cAAA,EAAgB,WAAA,EAAa,kBAAkB,IAAI,CAAA;AAAA,EACnG,cAAc,CAAC,cAAA,KAA2B,CAAC,cAAA,EAAgB,cAAA,EAAgB,gBAAgB,cAAc,CAAA;AAAA,EACzG,cAAA,EAAgB,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA;AAAA,EACxD,MAAA,EAAQ,CAAC,KAAA,EAAe,cAAA,KAAmC,CAAC,cAAA,EAAgB,QAAA,EAAU,cAAA,IAAkB,IAAA,EAAM,KAAK,CAAA;AAAA,EACnH,QAAA,EAAU,CAAC,UAAA,KAAyB,CAAC,cAAA,EAAgB,UAAA,EAAY,CAAC,GAAG,UAAU,CAAA,CAAE,IAAA,EAAM,CAAA;AAAA;AAAA,EAEvF,SAAS,CAAC,SAAA,KAAsB,CAAC,cAAA,EAAgB,WAAW,SAAS;AACvE;AAGO,IAAM,aAAA,GAAgB;AACtB,IAAM,kBAAA,GAAqB;AAKlC,SAAS,eAAe,CAAA,EAA+B;AACrD,EAAA,OAAO,OAAO,CAAA,KAAM,QAAA,IAAY,CAAA,KAAM,IAAA,IAAQ,MAAM,OAAA,CAAS,CAAA,CAAmB,QAAQ,CAAA,IAAK,YAAA,IAAgB,CAAA;AAC/G;AAGA,SAAS,UAAA,CAAW,MAAe,QAAA,EAAmD;AACpF,EAAA,OAAO,cAAA,CAAe,IAAI,CAAA,GAAI,EAAE,GAAG,IAAA,EAAM,QAAA,EAAU,cAAA,CAAe,IAAA,CAAK,QAAA,EAAU,QAAQ,CAAA,EAAE,GAAI,MAAA;AACjG;AAGO,SAAS,SAAA,CAAU,KAAyB,CAAA,EAAyB;AAC1E,EAAA,OACE,GAAA,CAAI,CAAC,CAAA,KAAM,cAAA,IACX,GAAA,CAAI,CAAC,CAAA,KAAM,cAAA,IACX,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,kBACb,GAAA,CAAI,CAAC,CAAA,KAAM,UAAA,IAAA,CACV,GAAA,CAAI,CAAC,CAAA,IAAK,IAAA,OAAW,EAAE,YAAA,IAAgB,IAAA,CAAA,KACvC,GAAA,CAAI,CAAC,CAAA,IAAK,IAAA,IAAQ,GAAA,CAAI,CAAC,MAAM,CAAA,CAAE,KAAA,CAAA;AAEpC;AAmCA,IAAM,WAAA,GAAc,cAAuC,IAAI,CAAA;AAkCxD,SAAS,YAAA,CAAa;AAAA,EAC3B,MAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAA;AAAA,EACA,cAAA;AAAA,EACA,UAAA;AAAA,EACA,WAAA;AAAA,EACA,eAAA;AAAA,EACA;AACF,CAAA,EAAsB;AACpB,EAAA,MAAM,cAAc,cAAA,EAAe;AACnC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA,CAA0B,EAAE,CAAA;AACtD,EAAA,MAAM,SAAA,GAAY,MAAA,iBAAO,IAAI,GAAA,EAA8B,CAAA;AAE3D,EAAA,MAAM,MAAA,GAAS,OAAA;AAAA,IACb,MACE,YAAA,CAAa;AAAA,MACX,MAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA,EAAO,WAAA;AAAA,MACP,QAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA;AAAA,MACA,WAAA;AAAA,MACA,cAAA;AAAA,MACA,QAAA,EAAU,QAAA;AAAA,MACV,MAAA,EAAQ,CAAC,OAAA,KAAY;AAGnB,QAAA,WAAA,CAAY,cAAA;AAAA,UACV,EAAE,WAAW,CAAC,CAAA,KAAM,UAAU,CAAA,CAAE,QAAA,EAAU,OAAO,CAAA,EAAE;AAAA,UACnD,CAAC,IAAA,KAAS,UAAA,CAAW,MAAM,CAAC,OAAO,CAAC,CAAA,IAAK;AAAA,SAC3C;AACA,QAAA,KAAK,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,CAAC,cAAA,EAAgB,WAAW,GAAG,CAAA;AAC9E,QAAA,KAAK,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,CAAC,cAAA,EAAgB,OAAO,GAAG,CAAA;AAAA,MAC5E;AAAA,KACD,CAAA;AAAA;AAAA;AAAA,IAGH,CAAC,QAAQ,MAAM;AAAA,GACjB;AAEA,EAAA,SAAA,CAAU,MAAM;AAGd,IAAA,KAAK,MAAA,CAAO,IAAA,EAAK,CAAE,IAAA,CAAK,MAAM,MAAA,CAAO,QAAA,EAAU,CAAA,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AACtE,IAAA,MAAM,MAAA,GAAS,MAAM,KAAK,MAAA,CAAO,UAAS,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AACjE,IAAA,MAAM,MAAA,GAAS,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,MAAA,CAAO,gBAAA,KAAqB,aAAa,MAAA,GAAS,IAAA;AACzG,IAAA,MAAA,EAAQ,gBAAA,CAAiB,UAAU,MAAM,CAAA;AACzC,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,cAAA,CAAe,MAAA,EAAQ,CAAC,MAAA,KAAW;AACpD,MAAA,KAAK,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,CAAC,cAAA,EAAgB,OAAO,GAAG,CAAA;AAC1E,MAAA,KAAK,YAAY,iBAAA,CAAkB,EAAE,UAAU,QAAA,CAAS,MAAA,IAAU,CAAA;AAClE,MAAA,KAAA,MAAW,CAAA,IAAK,SAAA,CAAU,OAAA,EAAS,CAAA,CAAE,MAAM,CAAA;AAAA,IAC7C,CAAC,CAAA;AACD,IAAA,OAAO,MAAM;AACX,MAAA,GAAA,CAAI,WAAA,EAAY;AAChB,MAAA,MAAA,EAAQ,mBAAA,CAAoB,UAAU,MAAM,CAAA;AAAA,IAC9C,CAAA;AAAA,EACF,GAAG,CAAC,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,WAAW,CAAC,CAAA;AAExC,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACZ,OAAO;AAAA,MACL,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA;AAAA,MACA,UAAA;AAAA,MACA,WAAA;AAAA,MACA,eAAA;AAAA,MACA,QAAA,EAAU,MAAM,MAAA,CAAO,QAAA,EAAS;AAAA,MAChC,WAAA,EAAa,MAAM,MAAA,CAAO,KAAA,EAAM;AAAA,MAChC,OAAA,EAAS,CAAC,CAAA,KAAM;AACd,QAAA,SAAA,CAAU,OAAA,CAAQ,IAAI,CAAC,CAAA;AACvB,QAAA,OAAO,MAAM,SAAA,CAAU,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA;AAAA,MACzC;AAAA,KACF,CAAA;AAAA,IACA,CAAC,MAAA,EAAQ,MAAA,EAAQ,QAAQ,KAAA,EAAO,UAAA,EAAY,aAAa,eAAe;AAAA,GAC1E;AACA,EAAA,uBAAO,GAAA,CAAC,WAAA,CAAY,QAAA,EAAZ,EAAqB,OAAe,QAAA,EAAS,CAAA;AACvD;AAEO,SAAS,OAAA,GAA4B;AAC1C,EAAA,MAAM,GAAA,GAAM,WAAW,WAAW,CAAA;AAClC,EAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,MAAM,8CAA8C,CAAA;AACxE,EAAA,OAAO,GAAA;AACT;AAGO,SAAS,QAAA,CAAS,IAAA,GAAyG,EAAC,EAAG;AACpI,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,OAAA,EAAQ;AAC3B,EAAA,MAAM,QAAQ,QAAA,CAAS,EAAE,QAAA,EAAU,QAAA,CAAS,MAAM,IAAI,CAAA,EAAG,OAAA,EAAS,MAAM,OAAO,KAAA,CAAM,IAAI,CAAA,EAAG,SAAA,EAAW,MAAQ,CAAA;AAC/G,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,CAAM,IAAA,IAAQ,EAAC,EAAG,SAAA,EAAW,KAAA,CAAM,SAAA,EAAW,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,OAAA,EAAS,MAAM,OAAA,EAAQ;AAC3G;AAGO,SAAS,cAAA,CAAe,IAAA,GAAoC,EAAC,EAAW;AAC7E,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,OAAA,EAAQ;AAC3B,EAAA,OACE,QAAA,CAAS,EAAE,QAAA,EAAU,CAAC,GAAG,QAAA,CAAS,MAAA,EAAO,EAAG,IAAA,CAAK,YAAA,IAAgB,EAAE,CAAA,EAAG,OAAA,EAAS,MAAM,MAAA,CAAO,WAAA,CAAY,IAAI,GAAG,SAAA,EAAW,IAAA,EAAQ,CAAA,CAAE,IAAA,IACpI,CAAA;AAEJ;AAaO,SAAS,eAAA,CAAgB,cAAA,EAA2C,IAAA,GAA+B,EAAC,EAAG;AAC5G,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,KAAA,KAAU,OAAA,EAAQ;AAClD,EAAA,MAAM,cAAc,cAAA,EAAe;AACnC,EAAA,MAAM,KAAK,cAAA,IAAkB,EAAA;AAC7B,EAAA,MAAM,OAAA,GAAU,QAAQ,cAAc,CAAA;AACtC,EAAA,MAAM,QAAA,GAAW,KAAK,QAAA,IAAY,EAAA;AAClC,EAAA,MAAM,WAAA,GAAc,OAAA;AAAA,IAClB,MAAM,CAAC,GAAG,QAAA,CAAS,YAAA,CAAa,EAAE,CAAA,EAAG,UAAA,EAAY,IAAA,CAAK,YAAA,IAAgB,IAAA,EAAM,IAAA,CAAK,SAAS,IAAI,CAAA;AAAA,IAC9F,CAAC,EAAA,EAAI,IAAA,CAAK,YAAA,EAAc,KAAK,KAAK;AAAA,GACpC;AAEA,EAAA,MAAM,YAAA,GAAe,QAAA,CAAS,EAAE,QAAA,EAAU,SAAS,YAAA,CAAa,EAAE,CAAA,EAAG,OAAA,EAAS,MAAM,MAAA,CAAO,YAAA,CAAa,EAAE,CAAA,EAAG,SAAS,CAAA;AACtH,EAAA,MAAM,UAAU,QAAA,CAAS,EAAE,UAAU,QAAA,CAAS,OAAA,CAAQ,EAAE,CAAA,EAAG,OAAA,EAAS,MAAM,MAAA,CAAO,OAAA,CAAQ,EAAE,CAAA,EAAG,OAAA,EAAS,WAAW,GAAA,EAAQ,cAAA,EAAgB,UAAU,CAAA;AACpJ,EAAA,MAAM,YAAY,QAAA,CAAS;AAAA,IACzB,UAAU,CAAC,cAAA,EAAgB,QAAQ,YAAA,CAAa,IAAA,EAAM,QAAQ,EAAE,CAAA;AAAA,IAChE,SAAS,MAAM,MAAA,CAAO,IAAA,CAAK,YAAA,CAAa,KAAM,IAAI,CAAA;AAAA,IAClD,OAAA,EAAS,OAAA,CAAQ,YAAA,CAAa,IAAA,EAAM,IAAI,CAAA;AAAA,IACxC,WAAW,EAAA,GAAK;AAAA,GACjB,CAAA;AACD,EAAA,MAAM,SAAS,QAAA,CAAS;AAAA,IACtB,QAAA,EAAU,QAAA,CAAS,IAAA,CAAK,EAAE,CAAA;AAAA,IAC1B,OAAA,EAAS,aAAa,EAAE,IAAA,EAAM,MAAM,MAAA,CAAO,MAAA,CAAO,EAAE,CAAA,EAAG,OAAA,EAAS,MAAM,MAAA,CAAO,OAAA,CAAQ,EAAE,CAAA,EAAE,CAAA;AAAA,IACzF,OAAA;AAAA,IACA,SAAA,EAAW;AAAA,GACZ,CAAA;AAED,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAAS,KAAK,CAAA;AAC1D,EAAA,MAAM,SAAA,GAAY,WAAA;AAAA,IAChB,MAAM,MAAA,CAAO,QAAA,CAAS,EAAA,EAAI,EAAE,KAAA,EAAO,QAAA,EAAU,YAAA,EAAc,IAAA,CAAK,YAAA,EAAc,KAAA,EAAO,IAAA,CAAK,OAAO,CAAA;AAAA,IACjG,CAAC,MAAA,EAAQ,EAAA,EAAI,UAAU,IAAA,CAAK,YAAA,EAAc,KAAK,KAAK;AAAA,GACtD;AAEA,EAAA,MAAM,QAAA,GAAW,QAAA,CAAuB,EAAE,QAAA,EAAU,WAAA,EAAa,SAAS,SAAA,EAAW,OAAA,EAAS,SAAA,EAAW,QAAA,EAAU,CAAA;AACnH,EAAA,MAAM,UAAA,GAAa,QAAA,CAAS,IAAA,EAAM,UAAA,IAAc,KAAA;AAEhD,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAAwB,IAAI,CAAA;AAExE,EAAA,MAAM,KAAA,GAAQ,WAAA;AAAA,IACZ,CAAC,QAAA,EAAyB,aAAA,KACxB,YAAY,YAAA,CAA2B,WAAA,EAAa,CAAC,IAAA,KAAS;AAC5D,MAAA,MAAM,OAAO,IAAA,IAAQ,EAAE,UAAU,EAAC,EAAG,YAAY,KAAA,EAAM;AACvD,MAAA,OAAO,EAAE,QAAA,EAAU,cAAA,CAAe,IAAA,CAAK,QAAA,EAAU,QAAQ,CAAA,EAAG,UAAA,EAAY,aAAA,IAAiB,IAAA,CAAK,UAAA,EAAW;AAAA,IAC3G,CAAC,CAAA;AAAA,IACH,CAAC,aAAa,WAAW;AAAA,GAC3B;AASA,EAAA,MAAM,OAAA,GAAU,YAAY,YAAY;AACtC,IAAA,MAAM,OAAA,GAAU,WAAA,CAAY,YAAA,CAA2B,WAAW,CAAA,IAAM,MAAM,WAAA,CAAY,eAAA,CAA8B,EAAE,QAAA,EAAU,WAAA,EAAa,OAAA,EAAS,WAAW,CAAA;AACrK,IAAA,MAAMA,QAAO,OAAA,CAAQ,QAAA;AACrB,IAAA,IAAI,KAAA,GAAQA,MAAK,MAAA,GAASA,KAAAA,CAAKA,MAAK,MAAA,GAAS,CAAC,EAAG,GAAA,GAAM,CAAA;AACvD,IAAA,KAAA,IAAS,QAAQ,CAAA,MAAO;AACtB,MAAA,MAAM,OAAO,MAAM,MAAA,CAAO,QAAA,CAAS,EAAA,EAAI,EAAE,QAAA,EAAU,KAAA,EAAO,KAAA,EAAO,aAAA,EAAe,cAAc,IAAA,CAAK,YAAA,EAAc,KAAA,EAAO,IAAA,CAAK,OAAO,CAAA;AACpI,MAAA,IAAI,IAAA,CAAK,QAAA,CAAS,MAAA,EAAQ,KAAA,CAAM,KAAK,QAAQ,CAAA;AAC7C,MAAA,IAAI,IAAA,CAAK,QAAA,CAAS,MAAA,GAAS,aAAA,EAAe;AAC1C,MAAA,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,GAAG,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,GAAG,CAAC,CAAA;AAC1D,MAAA,IAAI,EAAE,SAAS,kBAAA,EAAoB;AAAA,IACrC;AACA,IAAA,MAAM,MAAA,GAAS,MAAM,SAAA,EAAU;AAC/B,IAAA,WAAA,CAAY,YAAA,CAA2B,aAAa,EAAE,QAAA,EAAU,OAAO,QAAA,EAAU,UAAA,EAAY,MAAM,CAAA;AAAA,EACrG,CAAA,EAAG,CAAC,MAAA,EAAQ,SAAA,EAAW,EAAA,EAAI,KAAA,EAAO,WAAA,EAAa,IAAA,CAAK,YAAA,EAAc,IAAA,CAAK,KAAA,EAAO,WAAW,CAAC,CAAA;AAE1F,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,QAAA,CAAyB,EAAE,CAAA;AAC3D,EAAA,MAAM,OAAA,GAAU,OAAmC,IAAI,CAAA;AACvD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,MAAM,QAAA,GAAW,OAAO,SAAA,KAAsB;AAC5C,MAAA,MAAM,CAAA,GAAI,MAAM,MAAA,CAAO,OAAA,CAAQ,SAAS,CAAA;AACxC,MAAA,IAAI,CAAC,CAAA,EAAG;AACR,MAAA,MAAM,OAAA,GAAU,KAAK,YAAA,GAAe,CAAA,CAAE,iBAAiB,IAAA,CAAK,YAAA,GAAe,EAAE,YAAA,KAAiB,IAAA;AAC9F,MAAA,IAAI,OAAA,KAAY,CAAC,IAAA,CAAK,KAAA,IAAS,CAAA,CAAE,KAAA,KAAU,IAAA,CAAK,KAAA,CAAA,EAAQ,KAAA,CAAM,CAAC,CAAC,CAAC,CAAA;AACjE,MAAA,IAAI,CAAA,CAAE,YAAA,IAAgB,CAAC,IAAA,CAAK,YAAA,EAAc;AACxC,QAAA,MAAM,IAAA,GAAO,MAAM,MAAA,CAAO,OAAA,CAAQ,EAAE,YAAY,CAAA;AAChD,QAAA,IAAI,IAAA,EAAM,KAAA,CAAM,CAAC,IAAI,CAAC,CAAA;AAAA,MACxB;AAAA,IACF,CAAA;AACA,IAAA,OAAA,CAAQ,OAAA,GAAU,MAAA,CAAO,qBAAA,CAAsB,EAAA,EAAI,MAAA,EAAQ;AAAA,MACzD,QAAA,EAAU,CAAC,CAAA,KAAM;AACf,QAAA,IAAI,EAAE,IAAA,KAAS,SAAA,EAAW,KAAK,QAAA,CAAS,EAAE,EAAE,CAAA;AAAA,aAAA,IACnC,EAAE,IAAA,KAAS,UAAA,EAAY,KAAK,QAAA,CAAS,EAAE,SAAS,CAAA;AAAA,aAAA,IAChD,CAAA,CAAE,SAAS,SAAA,EAAW;AAC7B,UAAA,KAAK,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,SAAS,OAAA,CAAQ,EAAE,GAAG,CAAA;AACrE,UAAA,KAAK,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,SAAS,IAAA,CAAK,EAAE,GAAG,CAAA;AAAA,QACpE,CAAA,MAAA,IAAW,CAAA,CAAE,IAAA,KAAS,cAAA,EAAgB;AAEpC,UAAA,KAAK,WAAA,CAAY,iBAAA,CAAkB,EAAE,QAAA,EAAU,QAAA,CAAS,aAAa,EAAE,CAAA,EAAG,KAAA,EAAO,IAAA,EAAM,CAAA;AACvF,UAAA,KAAK,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,SAAS,MAAA,CAAO,EAAE,GAAG,CAAA;AACpE,UAAA,KAAK,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,SAAS,IAAA,CAAK,EAAE,GAAG,CAAA;AAAA,QACpE;AAAA,MACF,CAAA;AAAA,MACA,UAAA,EAAY,CAAC,CAAA,KAAM,WAAA,CAAY,CAAC,IAAA,KAAS,eAAA,CAAgB,CAAC,GAAG,IAAA,EAAM,CAAC,CAAC,CAAC,CAAA;AAAA,MACtE,QAAQ,MAAM,KAAK,SAAQ,CAAE,KAAA,CAAM,MAAM,MAAS;AAAA,KACnD,CAAA;AACD,IAAA,OAAO,MAAM;AACX,MAAA,OAAA,CAAQ,SAAS,WAAA,EAAY;AAC7B,MAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAAA,IACpB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,OAAA,EAAS,EAAA,EAAI,MAAA,EAAQ,KAAA,EAAO,OAAA,EAAS,WAAA,EAAa,IAAA,CAAK,YAAA,EAAc,IAAA,CAAK,KAAK,CAAC,CAAA;AAG5F,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AAC3B,IAAA,MAAM,CAAA,GAAI,UAAA,CAAW,MAAM,WAAA,CAAY,CAAC,SAAS,eAAA,CAAgB,IAAI,CAAC,CAAA,EAAG,GAAI,CAAA;AAC7E,IAAA,OAAO,MAAM,aAAa,CAAC,CAAA;AAAA,EAC7B,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,MAAM,QAAA,CAAS,IAAA,EAAM,QAAA,IAAY,EAAC,EAAG,CAAC,QAAA,CAAS,IAAI,CAAC,CAAA;AACzE,EAAA,MAAM,SAAA,GAAY,KAAK,MAAA,GAAS,IAAA,CAAK,KAAK,MAAA,GAAS,CAAC,EAAG,GAAA,GAAM,CAAA;AAG7D,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,mBAAA,GAAsB,OAAA,CAAQ,IAAA,EAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,MAAM,CAAA,EAAG,WAAA,GAAc,MAAA;AAC/G,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,cAAA,KAAmB,IAAA,IAAQ,UAAA,KAAe,MAAA,oBAA6B,UAAU,CAAA;AAAA,EACvF,CAAA,EAAG,CAAC,UAAA,EAAY,cAAc,CAAC,CAAA;AAE/B,EAAA,MAAM,QAAA,GAAW,YAAY,YAAY;AACvC,IAAA,IAAI,CAAC,OAAA,IAAW,SAAA,KAAc,CAAA,EAAG;AACjC,IAAA,MAAM,MAAA,CAAO,QAAA,CAAS,EAAA,EAAI,SAAS,CAAA;AACnC,IAAA,KAAK,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,CAAC,cAAA,EAAgB,OAAO,GAAG,CAAA;AAC1E,IAAA,KAAK,YAAY,iBAAA,CAAkB,EAAE,UAAU,QAAA,CAAS,MAAA,IAAU,CAAA;AAAA,EACpE,GAAG,CAAC,MAAA,EAAQ,SAAS,EAAA,EAAI,SAAA,EAAW,WAAW,CAAC,CAAA;AAKhD,EAAA,MAAM,UAAA,GAAa,KAAK,QAAA,KAAa,KAAA,IAAS,CAAC,IAAA,CAAK,YAAA,IAAgB,CAAC,IAAA,CAAK,KAAA;AAC1E,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,UAAA,IAAc,YAAY,CAAA,EAAG,KAAK,UAAS,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AAAA,EACxE,CAAA,EAAG,CAAC,UAAA,EAAY,SAAA,EAAW,QAAQ,CAAC,CAAA;AAEpC,EAAA,MAAM,WAAA,GAAc,YAAY,YAAY;AAC1C,IAAA,MAAM,KAAA,GAAQ,KAAK,CAAC,CAAA;AACpB,IAAA,IAAI,CAAC,SAAS,cAAA,EAAgB;AAC9B,IAAA,iBAAA,CAAkB,IAAI,CAAA;AACtB,IAAA,IAAI;AACF,MAAA,MAAM,OAAO,MAAM,MAAA,CAAO,QAAA,CAAS,EAAA,EAAI,EAAE,SAAA,EAAW,KAAA,CAAM,GAAA,EAAK,KAAA,EAAO,UAAU,YAAA,EAAc,IAAA,CAAK,cAAc,KAAA,EAAO,IAAA,CAAK,OAAO,CAAA;AACpI,MAAA,KAAA,CAAM,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,UAAU,CAAA;AAAA,IACtC,CAAA,SAAE;AACA,MAAA,iBAAA,CAAkB,KAAK,CAAA;AAAA,IACzB;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,EAAA,EAAI,IAAA,EAAM,cAAA,EAAgB,KAAA,EAAO,IAAA,CAAK,YAAA,EAAc,IAAA,CAAK,KAAA,EAAO,QAAQ,CAAC,CAAA;AAGrF,EAAA,MAAM,SAAA,GAAY,WAAA;AAAA,IAChB,OAAO,GAAA,KAAgB;AACrB,MAAA,IAAI,OAAA,GAAU,WAAA,CAAY,YAAA,CAA2B,WAAW,CAAA;AAChE,MAAA,KAAA,IAAS,KAAA,GAAQ,CAAA,EAAG,KAAA,GAAQ,EAAA,IAAM,SAAS,KAAA,EAAA,EAAS;AAClD,QAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,QAAA,CAAS,CAAC,CAAA;AAChC,QAAA,IAAI,CAAC,KAAA,IAAS,KAAA,CAAM,OAAO,GAAA,IAAO,CAAC,QAAQ,UAAA,EAAY;AACvD,QAAA,MAAM,IAAA,GAAO,MAAM,MAAA,CAAO,QAAA,CAAS,EAAA,EAAI;AAAA,UACrC,WAAW,KAAA,CAAM,GAAA;AAAA,UACjB,OAAO,IAAA,CAAK,GAAA,CAAI,KAAK,KAAA,CAAM,GAAA,GAAM,MAAM,EAAE,CAAA;AAAA,UACzC,cAAc,IAAA,CAAK,YAAA;AAAA,UACnB,OAAO,IAAA,CAAK;AAAA,SACb,CAAA;AACD,QAAA,KAAA,CAAM,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,UAAU,CAAA;AACpC,QAAA,OAAA,GAAU,WAAA,CAAY,aAA2B,WAAW,CAAA;AAAA,MAC9D;AACA,MAAA,OAAO,OAAA,CAAQ,SAAS,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,GAAA,KAAQ,GAAG,CAAC,CAAA;AAAA,IAC7D,CAAA;AAAA,IACA,CAAC,QAAQ,EAAA,EAAI,KAAA,EAAO,aAAa,IAAA,CAAK,YAAA,EAAc,IAAA,CAAK,KAAA,EAAO,WAAW;AAAA,GAC7E;AAEA,EAAA,MAAM,OAAA,GAAU,OAAA;AAAA,IACd,MACE,KAAA,CAAM,MAAA;AAAA,MACJ,CAAC,CAAA,KACC,CAAA,CAAE,MAAM,cAAA,KAAmB,EAAA,IAAA,CAC1B,EAAE,KAAA,CAAM,YAAA,IAAgB,WAAW,IAAA,CAAK,YAAA,IAAgB,UACxD,CAAC,IAAA,CAAK,SAAS,CAAA,CAAE,KAAA,CAAM,UAAU,IAAA,CAAK,KAAA;AAAA,KAC3C,CAAE,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,GAAG,CAAA,EAAG,KAAA,EAAO,CAAA,CAAE,YAAA,EAAa,CAAE,CAAA;AAAA,IAChD,CAAC,KAAA,EAAO,EAAA,EAAI,IAAA,CAAK,YAAA,EAAc,KAAK,KAAK;AAAA,GAC3C;AACA,EAAA,MAAM,KAAA,GAAsB,OAAA,CAAQ,MAAM,WAAA,CAAY,IAAA,EAAM,OAAO,CAAA,EAAG,CAAC,IAAA,EAAM,OAAO,CAAC,CAAA;AAErF,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,MAAM,QAAA,CAAS,MAAM,OAAA,CAAQ,OAAA,EAAS,MAAA,CAAO,QAAQ,CAAC,CAAA,EAAG,EAAE,CAAA;AACxF,EAAA,MAAM,eAAA,GAAkB,OAAA;AAAA,IACtB,MAAM,QAAA,CAAS,MAAM,QAAQ,OAAA,EAAS,MAAA,CAAO,iBAAiB,CAAC,CAAA;AAAA,IAC/D;AAAC,GACH;AACA,EAAA,MAAM,oBAAA,GAAuB,OAAA,CAAQ,MAAM,QAAA,CAAS,MAAM,OAAA,CAAQ,OAAA,EAAS,MAAA,CAAO,iBAAiB,CAAC,CAAA,EAAG,EAAE,CAAA;AAGzG,EAAA,MAAM,IAAA,GAAO,WAAA;AAAA,IACX,CAAC,OAAuD,KAAA,GAAqB,OAC3E,MAAA,CAAO,IAAA,CAAK,EAAE,GAAG,KAAA,EAAO,cAAA,EAAgB,IAAI,YAAA,EAAc,KAAA,CAAM,YAAA,IAAgB,IAAA,CAAK,YAAA,EAAc,KAAA,EAAO,MAAM,KAAA,IAAS,IAAA,CAAK,KAAA,EAAM,EAAG,KAAK,CAAA;AAAA,IAC9I,CAAC,MAAA,EAAQ,EAAA,EAAI,IAAA,CAAK,YAAA,EAAc,KAAK,KAAK;AAAA,GAC5C;AAEA,EAAA,OAAO;AAAA,IACL,YAAA,EAAc,aAAa,IAAA,IAAQ,IAAA;AAAA,IACnC,OAAA,EAAS,OAAA,CAAQ,IAAA,IAAQ,EAAC;AAAA,IAC1B,IAAA,EAAM,MAAA,CAAO,IAAA,EAAM,IAAA,IAAQ,IAAA;AAAA,IAC3B,OAAA,EAAS,MAAA,CAAO,IAAA,EAAM,OAAA,IAAW,KAAA;AAAA;AAAA,IAEjC,aAAA,EAAe,WAAW,MAAA,CAAO,SAAA;AAAA;AAAA,IAEjC,qBAAqB,YAAA,CAAa,SAAA;AAAA;AAAA,IAElC,WAAA,EAAa,OAAO,KAAA,IAAS,IAAA;AAAA,IAC7B,eAAe,MAAA,CAAO,OAAA;AAAA;AAAA,IAEtB,IAAA,EAAM,UAAU,IAAA,IAAQ,IAAA;AAAA;AAAA,IAExB,MAAA,EACE,MAAA,CAAO,IAAA,EAAM,IAAA,KAAS,WACtB,MAAA,CAAO,IAAA,EAAM,IAAA,KAAS,OAAA,IACrB,OAAO,IAAA,EAAM,IAAA,KAAS,QAAA,IAAY,SAAA,CAAU,MAAM,UAAA,KAAe,CAAA;AAAA;AAAA,IAEpE,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB,KAAA;AAAA,IACA,QAAA,EAAU,IAAA;AAAA,IACV,SAAA,EAAW,QAAA,CAAS,SAAA,IAAa,YAAA,CAAa,SAAA;AAAA,IAC9C,KAAA,EAAO,QAAA,CAAS,KAAA,IAAS,YAAA,CAAa,KAAA;AAAA,IACtC,UAAA;AAAA,IACA,cAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA;AAAA;AAAA,IAEA,eAAe,cAAA,KAAmB,IAAA,GAAO,OAAO,aAAA,CAAc,IAAA,EAAM,gBAAgB,MAAM,CAAA;AAAA,IAC1F,QAAA,EAAU,gBAAgB,QAAQ,CAAA;AAAA,IAClC,YAAA;AAAA,IACA,eAAA;AAAA;AAAA,IAEA,oBAAA;AAAA,IACA,QAAA;AAAA,IACA,IAAA;AAAA,IACA,OAAO,MAAA,CAAO,KAAA;AAAA,IACd,SAAS,MAAA,CAAO,OAAA;AAAA,IAChB,OAAA,EAAS;AAAA,GACX;AACF;AAKO,SAAS,iBAAA,GAAoB;AAClC,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,OAAA,EAAQ;AAC3B,EAAA,MAAM,cAAc,cAAA,EAAe;AACnC,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,MAAM,OAAA,GAAU,OAAO,cAAA,EAAwB,SAAA,KAAsB;AACnE,MAAA,MAAM,CAAA,GAAI,MAAM,MAAA,CAAO,OAAA,CAAQ,SAAS,CAAA;AACxC,MAAA,WAAA,CAAY,cAAA;AAAA,QAA6B,EAAE,QAAA,EAAU,QAAA,CAAS,YAAA,CAAa,cAAc,CAAA,EAAE;AAAA,QAAG,CAAC,SAC7F,CAAA,GAAK,UAAA,CAAW,MAAM,CAAC,CAAC,CAAC,CAAA,IAAK,IAAA,GAAQ;AAAA,OACxC;AAAA,IACF,CAAA;AACA,IAAA,OAAO;AAAA,MACL,MAAM,OAAO,CAAA,EAAgB,IAAA,EAAc,QAAA,GAAqB,EAAC,KAAM;AACrE,QAAA,MAAM,MAAA,CAAO,IAAA,CAAK,CAAA,CAAE,EAAA,EAAI,MAAM,QAAQ,CAAA;AACtC,QAAA,MAAM,OAAA,CAAQ,CAAA,CAAE,cAAA,EAAgB,CAAA,CAAE,EAAE,CAAA;AAAA,MACtC,CAAA;AAAA,MACA,MAAA,EAAQ,OAAO,CAAA,KAAmB;AAChC,QAAA,MAAM,MAAA,CAAO,MAAA,CAAO,CAAA,CAAE,EAAE,CAAA;AACxB,QAAA,MAAM,OAAA,CAAQ,CAAA,CAAE,cAAA,EAAgB,CAAA,CAAE,EAAE,CAAA;AAAA,MACtC,CAAA;AAAA,MACA,KAAA,EAAO,OAAO,CAAA,EAAgB,KAAA,EAAe,EAAA,KAAgB;AAC3D,QAAA,MAAM,MAAA,CAAO,KAAA,CAAM,CAAA,CAAE,EAAA,EAAI,OAAO,EAAE,CAAA;AAClC,QAAA,MAAM,OAAA,CAAQ,CAAA,CAAE,cAAA,EAAgB,CAAA,CAAE,EAAE,CAAA;AAAA,MACtC,CAAA;AAAA,MACA,GAAA,EAAK,OAAO,CAAA,EAAgB,EAAA,KAAgB;AAC1C,QAAA,MAAM,MAAA,CAAO,GAAA,CAAI,CAAA,CAAE,EAAA,EAAI,EAAE,CAAA;AACzB,QAAA,KAAK,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,SAAS,IAAA,CAAK,CAAA,CAAE,cAAc,CAAA,EAAG,CAAA;AAAA,MAClF,CAAA;AAAA,MACA,IAAA,EAAM,CAAC,CAAA,EAAgB,EAAA,EAAa,IAAA,KAAkB,OAAO,IAAA,CAAK,CAAA,CAAE,EAAA,EAAI,EAAA,EAAI,IAAI,CAAA;AAAA,MAChF,MAAA,EAAQ,CAAC,CAAA,EAAgB,MAAA,EAAoD,IAAA,KAAkB,OAAO,MAAA,CAAO,CAAA,CAAE,EAAA,EAAI,MAAA,EAAQ,IAAI;AAAA,KACjI;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,WAAW,CAAC,CAAA;AAC1B;AAMO,SAAS,UAAU,OAAA,EAAoE;AAC5F,EAAA,MAAM,EAAE,UAAA,EAAW,GAAI,OAAA,EAAQ;AAC/B,EAAA,MAAM,GAAA,GAAM,QAAQ,MAAM,CAAC,GAAG,IAAI,GAAA,CAAI,QAAQ,MAAA,CAAO,CAAC,MAAmB,OAAA,CAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAA,EAAK,EAAG,CAAC,OAAO,CAAC,CAAA;AACxG,EAAA,OACE,QAAA,CAAS;AAAA,IACP,QAAA,EAAU,CAAC,cAAA,EAAgB,QAAA,EAAU,GAAG,CAAA;AAAA,IACxC,OAAA,EAAS,MAAM,UAAA,CAAY,GAAG,CAAA;AAAA,IAC9B,OAAA,EAAS,OAAA,CAAQ,UAAU,CAAA,IAAK,IAAI,MAAA,GAAS,CAAA;AAAA,IAC7C,WAAW,CAAA,GAAI;AAAA,GAChB,CAAA,CAAE,IAAA,IAAQ,EAAC;AAEhB;AAGO,SAAS,UAAU,MAAA,EAAsD;AAC9E,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,OAAA,CAAQ,MAAM,CAAC,MAAM,CAAA,EAAG,CAAC,MAAM,CAAC,CAAC,CAAA;AAC1D,EAAA,OAAO,MAAA,GAAU,MAAA,CAAO,MAAM,CAAA,IAAK,IAAA,GAAQ,IAAA;AAC7C;AAGO,SAAS,QAAQ,cAAA,EAAqD;AAC3E,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,OAAA,EAAQ;AAC3B,EAAA,OACE,QAAA,CAAS,EAAE,QAAA,EAAU,QAAA,CAAS,KAAK,cAAA,IAAkB,EAAE,GAAG,OAAA,EAAS,MAAM,OAAO,IAAA,CAAK,cAAe,GAAG,OAAA,EAAS,OAAA,CAAQ,cAAc,CAAA,EAAG,CAAA,CAAE,IAAA,IAAQ,EAAC;AAExJ;AAGO,SAAS,YAAY,KAAA,EAA8D;AACxF,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,OAAA,EAAQ;AAC3B,EAAA,MAAM,OAAO,OAAA,CAAQ,MAAM,CAAC,GAAG,IAAI,IAAI,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,KAAmB,OAAA,CAAQ,CAAC,CAAC,CAAC,CAAC,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAC9F,EAAA,OACE,QAAA,CAAS,EAAE,QAAA,EAAU,QAAA,CAAS,MAAM,IAAI,CAAA,EAAG,OAAA,EAAS,MAAM,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,EAAG,OAAA,EAAS,IAAA,CAAK,MAAA,GAAS,CAAA,EAAG,SAAA,EAAW,KAAK,GAAA,EAAQ,CAAA,CAAE,IAAA,IAAQ,EAAC;AAElJ;AAGO,SAAS,oBAAA,GAAuB;AACrC,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,OAAA,EAAQ;AAC3B,EAAA,MAAM,cAAc,cAAA,EAAe;AACnC,EAAA,OAAO,WAAA;AAAA,IACL,OAAO,IAAA,EAAc,SAAA,EAAqB,IAAA,KAAwE;AAChH,MAAA,MAAM,KAAK,MAAM,MAAA,CAAO,iBAAA,CAAkB,IAAA,EAAM,WAAW,IAAI,CAAA;AAC/D,MAAA,KAAK,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,CAAC,cAAA,EAAgB,OAAO,GAAG,CAAA;AAC1E,MAAA,OAAO,EAAA;AAAA,IACT,CAAA;AAAA,IACA,CAAC,QAAQ,WAAW;AAAA,GACtB;AACF;AAQO,SAAS,UAAU,cAAA,EAA2C;AACnE,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,OAAA,EAAQ;AAC3B,EAAA,MAAM,cAAc,cAAA,EAAe;AACnC,EAAA,MAAM,KAAK,cAAA,IAAkB,EAAA;AAC7B,EAAA,MAAM,KAAA,GAAQ,SAAS,EAAE,QAAA,EAAU,SAAS,MAAA,CAAO,EAAE,GAAG,OAAA,EAAS,MAAM,OAAO,MAAA,CAAO,EAAE,GAAG,OAAA,EAAS,OAAA,CAAQ,cAAc,CAAA,EAAG,SAAA,EAAW,KAAQ,CAAA;AAC/I,EAAA,MAAM,QAAA,GAAW,WAAA;AAAA,IACf,OAAO,KAAA,EAAe,KAAA,GAAoB,EAAC,KAAM;AAC/C,MAAA,MAAM,MAAA,CAAO,QAAA,CAAS,EAAA,EAAI,KAAA,EAAO,KAAK,CAAA;AACtC,MAAA,MAAM,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,SAAS,MAAA,CAAO,EAAE,GAAG,CAAA;AAAA,IACvE,CAAA;AAAA,IACA,CAAC,MAAA,EAAQ,EAAA,EAAI,WAAW;AAAA,GAC1B;AACA,EAAA,MAAM,MAAA,GAAsB,KAAA,CAAM,IAAA,IAAQ,EAAC;AAC3C,EAAA,OAAO,EAAE,MAAA,EAAQ,SAAA,EAAW,KAAA,CAAM,SAAA,EAAW,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,QAAA,EAAU,OAAA,EAAS,KAAA,CAAM,OAAA,EAAQ;AACpG;AAOO,SAAS,YAAA,CAAa,cAAA,EAAgC,IAAA,GAA8B,EAAC,EAAG;AAC7F,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAO,GAAI,OAAA,EAAQ;AACnC,EAAA,MAAM,cAAc,cAAA,EAAe;AACnC,EAAA,MAAM,QAAQ,QAAA,CAAS;AAAA,IACrB,QAAA,EAAU,QAAA,CAAS,SAAA,CAAU,cAAc,CAAA;AAAA,IAC3C,OAAA,EAAS,MAAM,MAAA,CAAO,SAAA,CAAU,kBAAkB,MAAS,CAAA;AAAA,IAC3D,OAAA,EAAS,KAAK,OAAA,IAAW,IAAA;AAAA,IACzB,SAAA,EAAW;AAAA,GACZ,CAAA;AACD,EAAA,MAAM,OAAA,GAAU,WAAA,CAAY,MAAM,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,CAAC,cAAA,EAAgB,WAAW,CAAA,EAAG,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAC3H,EAAA,MAAM,QAAA,GAAW,WAAA;AAAA,IACf,OAAO,KAAA,EAAgE,KAAA,GAAqB,EAAC,KAAM;AACjG,MAAA,MAAM,cAAc,CAAC,GAAI,KAAA,CAAM,WAAA,IAAe,EAAG,CAAA;AACjD,MAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,QAAA,IAAI,OAAO,CAAA,CAAE,IAAA,KAAS,UAAU,MAAM,IAAI,MAAM,uDAAuD,CAAA;AACvG,QAAA,MAAM,OAAO,MAAM,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,gBAAgB,MAAA,EAAQ,CAAA,CAAE,IAAA,EAAM,EAAE,MAAM,CAAA,CAAE,IAAA,EAAM,WAAA,EAAa,CAAA,CAAE,aAAa,CAAA;AACnH,QAAA,WAAA,CAAY,IAAA,CAAK,EAAE,GAAG,CAAA,CAAE,MAAM,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,IAAA,IAAQ,iBAAA,CAAkB,CAAA,CAAE,WAAW,CAAA,EAAG,IAAA,EAAM,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,IAAA,IAAQ,EAAE,IAAA,EAAM,IAAA,EAAM,CAAA,CAAE,WAAA,EAAa,CAAA;AAAA,MACjJ;AACA,MAAA,MAAM,EAAA,GAAK,MAAM,MAAA,CAAO,QAAA,CAAS,EAAE,GAAG,KAAA,EAAO,QAAA,EAAU,KAAA,CAAM,QAAA,IAAY,QAAA,EAAS,EAAG,aAAa,CAAA;AAClG,MAAA,MAAM,OAAA,EAAQ;AACd,MAAA,OAAO,EAAA;AAAA,IACT,CAAA;AAAA,IACA,CAAC,MAAA,EAAQ,OAAA,EAAS,MAAM;AAAA,GAC1B;AACA,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,OAAO,EAAA,KAAe;AACpB,MAAA,WAAA,CAAY,cAAA,CAAmC,EAAE,QAAA,EAAU,CAAC,gBAAgB,WAAW,CAAA,EAAE,EAAG,CAAC,IAAA,KAAU,KAAA,CAAM,QAAQ,IAAI,CAAA,GAAI,KAAK,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,EAAE,CAAA,GAAI,IAAK,CAAA;AACpK,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,CAAO,gBAAgB,EAAE,CAAA;AAAA,MACjC,CAAA,SAAE;AACA,QAAA,MAAM,OAAA,EAAQ;AAAA,MAChB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,MAAA,EAAQ,WAAA,EAAa,OAAO;AAAA,GAC/B;AACA,EAAA,MAAM,KAAA,GAA4B,KAAA,CAAM,IAAA,IAAQ,EAAC;AACjD,EAAA,OAAO,EAAE,KAAA,EAAO,SAAA,EAAW,KAAA,CAAM,SAAA,EAAW,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,QAAA,EAAU,MAAA,EAAQ,OAAA,EAAS,KAAA,CAAM,OAAA,EAAQ;AAC3G;AAMO,SAAS,gBAAgB,cAAA,EAAgC;AAC9D,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAO,GAAI,OAAA,EAAQ;AACnC,EAAA,MAAM,cAAc,cAAA,EAAe;AACnC,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,EAAE,QAAA,EAAU,SAAS,cAAA,EAAe,EAAG,OAAA,EAAS,MAAM,OAAO,cAAA,EAAe,EAAG,SAAA,EAAW,CAAA,GAAI,KAAQ,CAAA;AAC5H,EAAA,MAAM,UAAU,QAAA,CAAS;AAAA,IACvB,QAAA,EAAU,QAAA,CAAS,YAAA,CAAa,cAAA,IAAkB,EAAE,CAAA;AAAA,IACpD,OAAA,EAAS,MAAM,MAAA,CAAO,eAAA,CAAgB,cAAe,CAAA;AAAA,IACrD,OAAA,EAAS,QAAQ,cAAc,CAAA;AAAA,IAC/B,WAAW,CAAA,GAAI;AAAA,GAChB,CAAA;AACD,EAAA,MAAM,OAAA,GAAU,WAAA;AAAA,IACd,OAAO,KAAA,KAA6B;AAClC,MAAA,MAAM,MAAA,CAAO,gBAAgB,KAAK,CAAA;AAClC,MAAA,MAAM,YAAY,iBAAA,CAAkB,EAAE,UAAU,QAAA,CAAS,cAAA,IAAkB,CAAA;AAC3E,MAAA,MAAM,YAAY,iBAAA,CAAkB,EAAE,SAAA,EAAW,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,CAAC,CAAA,KAAM,kBAAkB,CAAA,CAAE,QAAA,CAAS,CAAC,CAAA,KAAM,gBAAgB,CAAA;AAAA,IAChI,CAAA;AAAA,IACA,CAAC,QAAQ,WAAW;AAAA,GACtB;AACA,EAAA,MAAM,IAAA,GAA2B,OAAA,CAAQ,IAAA,IAAQ,EAAC;AAClD,EAAA,MAAM,OAAA,GAAU,WAAA,CAAY,CAAC,EAAA,KAAe,KAAK,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,EAAE,CAAA,IAAK,IAAA,EAAM,CAAC,IAAI,CAAC,CAAA;AAC7F,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,KAAK,IAAA,IAAQ,IAAA;AAAA,IACnB,aAAa,IAAA,CAAK,SAAA;AAAA,IAClB,OAAA;AAAA,IACA,OAAA,EAAS,IAAA;AAAA;AAAA,IAET,QAAQ,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,MAAM,CAAA;AAAA,IAC9C,OAAA;AAAA,IACA,WAAW,IAAA,CAAK,SAAA,IAAc,OAAA,CAAQ,cAAc,KAAK,OAAA,CAAQ;AAAA,GACnE;AACF;AAGO,SAAS,YAAA,GAAe;AAC7B,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,OAAA,EAAQ;AAC3B,EAAA,MAAM,cAAc,cAAA,EAAe;AACnC,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,IAAA,GAAO,WAAA;AAAA,IACX,OAAO,eAAA,EAA2B,IAAA,EAAc,IAAA,GAAgD,EAAC,KAAM;AACrG,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAM,MAAM,MAAA,CAAO,SAAA,CAAU,iBAAiB,IAAA,CAAK,QAAA,IAAY,QAAA,EAAS,EAAG,IAAA,EAAM,EAAE,MAAA,EAAQ,IAAA,CAAK,QAAQ,CAAA;AAC9G,QAAA,KAAK,WAAA,CAAY,kBAAkB,EAAE,QAAA,EAAU,CAAC,cAAA,EAAgB,OAAO,GAAG,CAAA;AAC1E,QAAA,OAAO,GAAA;AAAA,MACT,CAAA,SAAE;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,QAAQ,WAAW;AAAA,GACtB;AACA,EAAA,OAAO,EAAE,MAAM,OAAA,EAAQ;AACzB;AAGO,SAAS,iBAAA,CAAqB,OAAU,EAAA,EAAe;AAC5D,EAAA,MAAM,CAAC,CAAA,EAAG,IAAI,CAAA,GAAI,SAAS,KAAK,CAAA;AAChC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,MAAM,CAAA,EAAG;AACX,MAAA,IAAA,CAAK,KAAK,CAAA;AACV,MAAA;AAAA,IACF;AACA,IAAA,MAAM,IAAI,UAAA,CAAW,MAAM,IAAA,CAAK,KAAK,GAAG,EAAE,CAAA;AAC1C,IAAA,OAAO,MAAM,aAAa,CAAC,CAAA;AAAA,EAC7B,CAAA,EAAG,CAAC,KAAA,EAAO,EAAE,CAAC,CAAA;AACd,EAAA,OAAO,EAAA,IAAM,IAAI,KAAA,GAAQ,CAAA;AAC3B;AAGO,SAAS,SAAA,CAAU,KAAA,EAAe,IAAA,GAAgF,EAAC,EAAG;AAC3H,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,OAAA,EAAQ;AAC3B,EAAA,MAAM,IAAI,iBAAA,CAAkB,KAAA,CAAM,MAAK,EAAG,IAAA,CAAK,cAAc,GAAG,CAAA;AAChE,EAAA,MAAM,OAAA,GAAU,EAAE,MAAA,IAAU,CAAA;AAC5B,EAAA,MAAM,SAAS,QAAA,CAAS;AAAA,IACtB,QAAA,EAAU,QAAA,CAAS,MAAA,CAAO,CAAA,EAAG,KAAK,cAAc,CAAA;AAAA,IAChD,OAAA,EAAS,MAAM,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,IAAA,CAAK,cAAA,IAAkB,IAAA,EAAM,EAAE,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,CAAA;AAAA,IAClF,OAAA;AAAA,IACA,SAAA,EAAW;AAAA,GACZ,CAAA;AACD,EAAA,MAAM,OAAwB,OAAA,GAAW,MAAA,CAAO,IAAA,IAAQ,KAAM,EAAC;AAC/D,EAAA,OAAO;AAAA,IACL,IAAA;AAAA;AAAA,IAEA,KAAA,EAAO,CAAA;AAAA;AAAA,IAEP,aAAa,KAAA,CAAM,IAAA,EAAK,KAAM,CAAA,IAAM,WAAW,MAAA,CAAO,UAAA;AAAA,IACtD,OAAO,MAAA,CAAO;AAAA,GAChB;AACF;AAGO,IAAM,wBAAA,GAA2B;AACxC,IAAM,YAAA,uBAAmB,OAAA,EAA2D;AAGpF,SAAS,KAAK,GAAA,EAAa;AACzB,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,MAAM,UAA0B,EAAC;AACjC,EAAA,OAAO,OAAW,IAAA,KAAuC;AACvD,IAAA,IAAI,MAAA,IAAU,GAAA,EAAK,MAAM,IAAI,OAAA,CAAc,CAAC,CAAA,KAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAC,CAAA;AACjE,IAAA,MAAA,IAAU,CAAA;AACV,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,IAAA,EAAK;AAAA,IACpB,CAAA,SAAE;AACA,MAAA,MAAA,IAAU,CAAA;AACV,MAAA,OAAA,CAAQ,OAAM,IAAI;AAAA,IACpB;AAAA,EACF,CAAA;AACF;AASO,SAAS,gBAAgB,UAAA,EAA8E;AAC5G,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,OAAA,EAAQ;AAC3B,EAAA,MAAM,IAAA,GAAO,QAAQ,MAAM,CAAC,GAAG,IAAI,GAAA,CAAI,WAAW,MAAA,CAAO,CAAC,MAAmB,OAAA,CAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAA,EAAK,EAAG,CAAC,UAAU,CAAC,CAAA;AAC/G,EAAA,IAAI,GAAA,GAAM,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AACjC,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,GAAA,GAAM,KAAK,wBAAwB,CAAA;AACnC,IAAA,YAAA,CAAa,GAAA,CAAI,QAAQ,GAAG,CAAA;AAAA,EAC9B;AACA,EAAA,MAAM,OAAA,GAAU,GAAA;AAChB,EAAA,MAAM,OAAA,GAAU,WAAA;AAAA,IACd,CAAC,OAAA,KAA4C;AAC3C,MAAA,IAAI,GAAA,GAAgD,IAAA;AACpD,MAAA,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA,KAAM;AACxB,QAAA,IAAI,CAAA,CAAE,IAAA,EAAM,CAAC,GAAA,KAAQ,IAAI,IAAA,CAAK,CAAC,CAAE,CAAA,GAAI,CAAA,CAAE,IAAA;AAAA,MACzC,CAAC,CAAA;AACD,MAAA,OAAO,GAAA,IAAO,cAAA;AAAA,IAChB,CAAA;AAAA,IACA,CAAC,IAAI;AAAA,GACP;AACA,EAAA,OAAO,UAAA,CAAW;AAAA,IAChB,OAAA,EAAS,IAAA,CAAK,GAAA,CAAI,CAAC,EAAA,MAAQ;AAAA,MACzB,QAAA,EAAU,QAAA,CAAS,OAAA,CAAQ,EAAE,CAAA;AAAA,MAC7B,OAAA,EAAS,MAAM,OAAA,CAAQ,YAAA,CAAa,MAAM,MAAA,CAAO,eAAA,CAAgB,CAAC,EAAE,CAAC,CAAA,EAAG,EAAE,CAAA,IAAK,EAAE,CAAA;AAAA,MACjF,SAAA,EAAW,GAAA;AAAA,MACX,eAAA,EAAiB,CAAC,CAAA,KAChB,CAAC,CAAA,CAAE,KAAA,CAAM,IAAA,EAAM,MAAA,IAAU,CAAA,CAAE,KAAA,CAAM,eAAA,GAAkB,CAAA,GAAI,GAAA,GAAQ;AAAA,KACnE,CAAE,CAAA;AAAA,IACF;AAAA,GACD,CAAA;AACH;AACA,IAAM,iBAAoD,EAAC","file":"chunk-PZTOY2LY.js","sourcesContent":["/**\n * @feat:chat\n * React hooks for chat, the same on web and native: the inbox, one\n * conversation (its messages, members, your role, who is typing) kept live by\n * Realtime hints, sending through the outbox, and message actions. Server\n * state is TanStack Query's; the renderers in /web and /native only draw.\n */\nimport { useQueries, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport type { ChatClient, ConversationChannel, MessagePage } from \"../core/client\";\nimport { createOutbox, type LocalFile, type Outbox, type OutboxStore, type QueuedMessage } from \"../core/outbox\";\nimport { currentActivity, firstUnreadId, mergeThread, throttle, upsertMessages, type ThreadItem } from \"../core/thread\";\nimport { attachmentKindFor, randomId } from \"../core/media\";\nimport type {\n AvailabilityInput,\n ChatActivity,\n ChatAttachment,\n ChatAvailability,\n ChatLinkPreview,\n ChatMessage,\n ChatSearchHit,\n ChatSignal,\n ChatTopic,\n ScheduleInput,\n ScheduledMessage,\n SendInput,\n TopicInput,\n} from \"../core/types\";\n\nexport const chatKeys = {\n all: [\"genesis-chat\"] as const,\n inbox: (opts?: object) => [\"genesis-chat\", \"inbox\", opts ?? {}] as const,\n unread: () => [\"genesis-chat\", \"unread\"] as const,\n conversation: (id: string) => [\"genesis-chat\", \"conversation\", id] as const,\n members: (id: string) => [\"genesis-chat\", \"members\", id] as const,\n role: (id: string) => [\"genesis-chat\", \"role\", id] as const,\n pins: (id: string) => [\"genesis-chat\", \"pins\", id] as const,\n files: (paths: string[]) => [\"genesis-chat\", \"files\", [...paths].sort()] as const,\n /** Under the conversation's key, so its `conversation` hint (set_topic broadcasts one) refreshes them. */\n topics: (id: string) => [\"genesis-chat\", \"conversation\", id, \"topics\"] as const,\n scheduled: (conversationId?: string | null) => [\"genesis-chat\", \"scheduled\", conversationId ?? null] as const,\n availability: (conversationId: string) => [\"genesis-chat\", \"conversation\", conversationId, \"availability\"] as const,\n myAvailability: () => [\"genesis-chat\", \"my-availability\"] as const,\n search: (query: string, conversationId?: string | null) => [\"genesis-chat\", \"search\", conversationId ?? null, query] as const,\n previews: (messageIds: string[]) => [\"genesis-chat\", \"previews\", [...messageIds].sort()] as const,\n /** One message's link previews (useLinkPreviews keeps one query per message). */\n preview: (messageId: string) => [\"genesis-chat\", \"preview\", messageId] as const,\n};\n\n/** How many messages a catch-up asks for at a time, and how many pages before it gives up and jumps to the newest. */\nexport const CATCH_UP_PAGE = 200;\nexport const CATCH_UP_MAX_PAGES = 10;\n\n/** What a messages list holds in the query cache: the page, and whether older messages exist. */\nexport type MessagesData = MessagePage;\n\nfunction isMessagesData(v: unknown): v is MessagesData {\n return typeof v === \"object\" && v !== null && Array.isArray((v as MessagesData).messages) && \"hasEarlier\" in v;\n}\n\n/** Adds or replaces messages in a cached list, keeping `hasEarlier`. */\nfunction upsertInto(prev: unknown, incoming: ChatMessage[]): MessagesData | undefined {\n return isMessagesData(prev) ? { ...prev, messages: upsertMessages(prev.messages, incoming) } : undefined;\n}\n\n/** Does a messages list (`[genesis-chat, conversation, id, messages, threadRootId, topic]`) hold this message? */\nexport function belongsTo(key: readonly unknown[], m: ChatMessage): boolean {\n return (\n key[0] === \"genesis-chat\" &&\n key[1] === \"conversation\" &&\n key[2] === m.conversationId &&\n key[3] === \"messages\" &&\n (key[4] ?? null) === (m.threadRootId ?? null) &&\n (key[5] == null || key[5] === m.topic)\n );\n}\n\n/**\n * Hosted video uploads (Mux direct uploads): your server makes an upload and returns its URL;\n * the renderer PUTs the recording there and sends `{ kind: \"video\", provider: \"mux\", providerId,\n * status: \"pending\" }`. Without it, videos go to storage as files.\n */\nexport interface ChatVideoUpload {\n create(conversationId: string): Promise<{ url: string; providerId: string; provider: \"mux\" }>;\n}\n\n/** What an app attachment kind's card shows (a form, a check-in): plain data, drawn by each renderer. */\nexport interface ChatAttachmentKind {\n title(attachment: ChatAttachment): string;\n subtitle?(attachment: ChatAttachment): string | null | undefined;\n /** The card's button: a link (`href`) or a handler (`onPress`). */\n action?(attachment: ChatAttachment): { label: string; href?: string; onPress?: () => void } | null | undefined;\n}\n\ninterface ChatContextValue {\n client: ChatClient;\n userId: string;\n outbox: Outbox;\n queue: QueuedMessage[];\n loadPeople?: (userIds: string[]) => Promise<Record<string, ChatPerson>>;\n videoUpload?: ChatVideoUpload;\n attachmentKinds?: Record<string, ChatAttachmentKind>;\n /** Retries every failed message (call it when the connection comes back: AppState, NetInfo). */\n retryAll(): Promise<void>;\n /** Forgets every unsent message, here and in the outbox store (call it on sign-out). */\n clearOutbox(): Promise<void>;\n /** Subscribe to inbox hints (one channel for the app, fanned out here). */\n onInbox(listener: (s: ChatSignal) => void): () => void;\n}\n\nconst ChatContext = createContext<ChatContextValue | null>(null);\n\nexport interface ChatProviderProps {\n client: ChatClient;\n /** The signed-in user's id (auth.users). */\n userId: string;\n /** Where unsent messages wait between launches (localStorage, MMKV…). Memory by default. */\n outboxStore?: OutboxStore;\n /** Reads a native file URI for upload. */\n readFile?: (uri: string) => Promise<Blob | ArrayBuffer>;\n /** Replaces the plain upload (resumable uploads on native). `signal` aborts when the message is discarded or the outbox cleared. */\n upload?: (conversationId: string, file: LocalFile, opts?: { signal?: AbortSignal }) => Promise<string>;\n /** Makes client ids (default: a random UUID that works without crypto.randomUUID). */\n newId?: () => string;\n /** Keeps a byte file (a pasted image, a recording) across launches: write it, return a URI. */\n persistFile?: (file: LocalFile) => Promise<string>;\n /** Lets go of a sent file's preview (default: revokes `blob:` object URLs only). */\n releasePreview?: (url: string) => void;\n /** Names and avatars for user ids, fetched in batches (usePeople, usePerson). */\n loadPeople?: (userIds: string[]) => Promise<Record<string, ChatPerson>>;\n /** Hosted video uploads (Mux); videos go to storage without it. */\n videoUpload?: ChatVideoUpload;\n /** Cards for your own attachment kinds (`{ form: { title, subtitle, action } }`). */\n attachmentKinds?: Record<string, ChatAttachmentKind>;\n children: ReactNode;\n}\n\n/** How the app shows a person. */\nexport interface ChatPerson {\n name: string;\n avatarUrl?: string | null;\n}\n\n/** Puts chat in reach of every hook below it. Needs a TanStack QueryClientProvider above it. */\nexport function ChatProvider({\n client,\n userId,\n outboxStore,\n readFile,\n upload,\n newId,\n persistFile,\n releasePreview,\n loadPeople,\n videoUpload,\n attachmentKinds,\n children,\n}: ChatProviderProps) {\n const queryClient = useQueryClient();\n const [queue, setQueue] = useState<QueuedMessage[]>([]);\n const listeners = useRef(new Set<(s: ChatSignal) => void>());\n\n const outbox = useMemo(\n () =>\n createOutbox({\n client,\n userId,\n store: outboxStore,\n readFile,\n upload,\n newId,\n persistFile,\n releasePreview,\n onChange: setQueue,\n onSent: (message) => {\n // Into every loaded list it belongs to (the main timeline, its thread, its topic), so\n // the bubble never disappears between the send landing and the live hint.\n queryClient.setQueriesData<MessagesData>(\n { predicate: (q) => belongsTo(q.queryKey, message) },\n (prev) => upsertInto(prev, [message]) ?? prev,\n );\n void queryClient.invalidateQueries({ queryKey: [\"genesis-chat\", \"scheduled\"] });\n void queryClient.invalidateQueries({ queryKey: [\"genesis-chat\", \"inbox\"] });\n },\n }),\n // A new person or client is a new outbox.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [client, userId],\n );\n\n useEffect(() => {\n // What a relaunch left unsent is retried once (messages waiting for a lost file stay put),\n // and again whenever the browser comes back online.\n void outbox.load().then(() => outbox.retryAll()).catch(() => undefined);\n const online = () => void outbox.retryAll().catch(() => undefined);\n const target = typeof window !== \"undefined\" && typeof window.addEventListener === \"function\" ? window : null;\n target?.addEventListener(\"online\", online);\n const sub = client.subscribeInbox(userId, (signal) => {\n void queryClient.invalidateQueries({ queryKey: [\"genesis-chat\", \"inbox\"] });\n void queryClient.invalidateQueries({ queryKey: chatKeys.unread() });\n for (const l of listeners.current) l(signal);\n });\n return () => {\n sub.unsubscribe();\n target?.removeEventListener(\"online\", online);\n };\n }, [client, userId, outbox, queryClient]);\n\n const value = useMemo<ChatContextValue>(\n () => ({\n client,\n userId,\n outbox,\n queue,\n loadPeople,\n videoUpload,\n attachmentKinds,\n retryAll: () => outbox.retryAll(),\n clearOutbox: () => outbox.clear(),\n onInbox: (l) => {\n listeners.current.add(l);\n return () => listeners.current.delete(l);\n },\n }),\n [client, userId, outbox, queue, loadPeople, videoUpload, attachmentKinds],\n );\n return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;\n}\n\nexport function useChat(): ChatContextValue {\n const ctx = useContext(ChatContext);\n if (!ctx) throw new Error(\"Chat hooks need a <ChatProvider> above them.\");\n return ctx;\n}\n\n/** Your conversations, live. `kind` narrows to one kind; `archived` shows the archived ones. */\nexport function useInbox(opts: { kind?: string; kinds?: string[]; excludeKinds?: string[]; archived?: boolean; limit?: number } = {}) {\n const { client } = useChat();\n const query = useQuery({ queryKey: chatKeys.inbox(opts), queryFn: () => client.inbox(opts), staleTime: 15_000 });\n return { items: query.data ?? [], isLoading: query.isLoading, error: query.error, refetch: query.refetch };\n}\n\n/** How many messages wait for you everywhere (for a badge), live. */\nexport function useUnreadTotal(opts: { excludeKinds?: string[] } = {}): number {\n const { client } = useChat();\n return (\n useQuery({ queryKey: [...chatKeys.unread(), opts.excludeKinds ?? []], queryFn: () => client.unreadTotal(opts), staleTime: 15_000 }).data ??\n 0\n );\n}\n\nexport interface UseConversationOptions {\n /** Mark messages read as they arrive while this is true (the thread is on screen). Default true. */\n markRead?: boolean;\n /** One thread's replies instead of the main timeline. */\n threadRootId?: string;\n /** One topic's messages. */\n topic?: string;\n pageSize?: number;\n}\n\n/** One conversation: its messages (with what you are still sending), members, your role, and who is typing. */\nexport function useConversation(conversationId: string | null | undefined, opts: UseConversationOptions = {}) {\n const { client, userId, outbox, queue } = useChat();\n const queryClient = useQueryClient();\n const id = conversationId ?? \"\";\n const enabled = Boolean(conversationId);\n const pageSize = opts.pageSize ?? 50;\n const messagesKey = useMemo(\n () => [...chatKeys.conversation(id), \"messages\", opts.threadRootId ?? null, opts.topic ?? null] as const,\n [id, opts.threadRootId, opts.topic],\n );\n\n const conversation = useQuery({ queryKey: chatKeys.conversation(id), queryFn: () => client.conversation(id), enabled });\n const members = useQuery({ queryKey: chatKeys.members(id), queryFn: () => client.members(id), enabled, staleTime: 60_000, refetchOnMount: \"always\" });\n const kindRules = useQuery({\n queryKey: [\"genesis-chat\", \"kind\", conversation.data?.kind ?? \"\"],\n queryFn: () => client.kind(conversation.data!.kind),\n enabled: Boolean(conversation.data?.kind),\n staleTime: 10 * 60_000,\n });\n const access = useQuery({\n queryKey: chatKeys.role(id),\n queryFn: async () => ({ role: await client.myRole(id), canPost: await client.canPost(id) }),\n enabled,\n staleTime: 60_000,\n });\n\n const [loadingEarlier, setLoadingEarlier] = useState(false);\n const fetchPage = useCallback(\n () => client.messages(id, { limit: pageSize, threadRootId: opts.threadRootId, topic: opts.topic }),\n [client, id, pageSize, opts.threadRootId, opts.topic],\n );\n // Kept fresh by live hints and the catch-up on every join, so never refetched on its own.\n const messages = useQuery<MessagesData>({ queryKey: messagesKey, queryFn: fetchPage, enabled, staleTime: Infinity });\n const hasEarlier = messages.data?.hasEarlier ?? false;\n // Where \"New\" goes: what you had read when the conversation opened.\n const [readWhenOpened, setReadWhenOpened] = useState<number | null>(null);\n\n const merge = useCallback(\n (incoming: ChatMessage[], hasEarlierNow?: boolean) =>\n queryClient.setQueryData<MessagesData>(messagesKey, (prev) => {\n const base = prev ?? { messages: [], hasEarlier: false };\n return { messages: upsertMessages(base.messages, incoming), hasEarlier: hasEarlierNow ?? base.hasEarlier };\n }),\n [queryClient, messagesKey],\n );\n\n /**\n * Fetches what came after the newest message held (everything, for an empty list). Runs on\n * every join of the live channel, the first included, so messages that arrived while the\n * thread was closed, or between the first fetch and the subscription, always show. Pages\n * until a short one; after CATCH_UP_MAX_PAGES full pages (a long time away from a busy group)\n * it shows the newest page instead, with the rest behind \"load earlier\".\n */\n const catchUp = useCallback(async () => {\n const current = queryClient.getQueryData<MessagesData>(messagesKey) ?? (await queryClient.ensureQueryData<MessagesData>({ queryKey: messagesKey, queryFn: fetchPage }));\n const list = current.messages;\n let after = list.length ? list[list.length - 1]!.seq : 0;\n for (let pages = 0; ; ) {\n const page = await client.messages(id, { afterSeq: after, limit: CATCH_UP_PAGE, threadRootId: opts.threadRootId, topic: opts.topic });\n if (page.messages.length) merge(page.messages);\n if (page.messages.length < CATCH_UP_PAGE) return;\n after = Math.max(after, ...page.messages.map((m) => m.seq));\n if (++pages >= CATCH_UP_MAX_PAGES) break;\n }\n const newest = await fetchPage();\n queryClient.setQueryData<MessagesData>(messagesKey, { messages: newest.messages, hasEarlier: true });\n }, [client, fetchPage, id, merge, messagesKey, opts.threadRootId, opts.topic, queryClient]);\n\n const [activity, setActivity] = useState<ChatActivity[]>([]);\n const channel = useRef<ConversationChannel | null>(null);\n useEffect(() => {\n if (!enabled) return;\n const fetchOne = async (messageId: string) => {\n const m = await client.message(messageId);\n if (!m) return;\n const belongs = opts.threadRootId ? m.threadRootId === opts.threadRootId : m.threadRootId === null;\n if (belongs && (!opts.topic || m.topic === opts.topic)) merge([m]);\n if (m.threadRootId && !opts.threadRootId) {\n const root = await client.message(m.threadRootId);\n if (root) merge([root]);\n }\n };\n channel.current = client.subscribeConversation(id, userId, {\n onSignal: (s) => {\n if (s.type === \"message\") void fetchOne(s.id);\n else if (s.type === \"reaction\") void fetchOne(s.messageId);\n else if (s.type === \"members\") {\n void queryClient.invalidateQueries({ queryKey: chatKeys.members(id) });\n void queryClient.invalidateQueries({ queryKey: chatKeys.role(id) });\n } else if (s.type === \"conversation\") {\n // The conversation row, its topics and availability (not its messages, kept by hints).\n void queryClient.invalidateQueries({ queryKey: chatKeys.conversation(id), exact: true });\n void queryClient.invalidateQueries({ queryKey: chatKeys.topics(id) });\n void queryClient.invalidateQueries({ queryKey: chatKeys.pins(id) });\n }\n },\n onActivity: (a) => setActivity((prev) => currentActivity([...prev, a])),\n onJoin: () => void catchUp().catch(() => undefined),\n });\n return () => {\n channel.current?.unsubscribe();\n channel.current = null;\n };\n }, [client, enabled, id, userId, merge, catchUp, queryClient, opts.threadRootId, opts.topic]);\n\n // Activity fades after a few seconds without news.\n useEffect(() => {\n if (activity.length === 0) return;\n const t = setTimeout(() => setActivity((prev) => currentActivity(prev)), 2000);\n return () => clearTimeout(t);\n }, [activity]);\n\n const list = useMemo(() => messages.data?.messages ?? [], [messages.data]);\n const latestSeq = list.length ? list[list.length - 1]!.seq : 0;\n // Where \"New\" goes comes from your read place as fetched for this opening, never a cached\n // copy from an earlier visit (which would draw the line above messages already read).\n const myLastRead = members.isFetchedAfterMount ? members.data?.find((m) => m.userId === userId)?.lastReadSeq : undefined;\n useEffect(() => {\n if (readWhenOpened === null && myLastRead !== undefined) setReadWhenOpened(myLastRead);\n }, [myLastRead, readWhenOpened]);\n\n const markRead = useCallback(async () => {\n if (!enabled || latestSeq === 0) return;\n await client.markRead(id, latestSeq);\n void queryClient.invalidateQueries({ queryKey: [\"genesis-chat\", \"inbox\"] });\n void queryClient.invalidateQueries({ queryKey: chatKeys.unread() });\n }, [client, enabled, id, latestSeq, queryClient]);\n\n // Your read place is one number for the whole conversation, so only the whole timeline moves\n // it: a topic (or a thread) shows a slice, and marking up to its newest message would also\n // mark unseen messages in other topics read.\n const shouldMark = opts.markRead !== false && !opts.threadRootId && !opts.topic;\n useEffect(() => {\n if (shouldMark && latestSeq > 0) void markRead().catch(() => undefined);\n }, [shouldMark, latestSeq, markRead]);\n\n const loadEarlier = useCallback(async () => {\n const first = list[0];\n if (!first || loadingEarlier) return;\n setLoadingEarlier(true);\n try {\n const page = await client.messages(id, { beforeSeq: first.seq, limit: pageSize, threadRootId: opts.threadRootId, topic: opts.topic });\n merge(page.messages, page.hasEarlier);\n } finally {\n setLoadingEarlier(false);\n }\n }, [client, id, list, loadingEarlier, merge, opts.threadRootId, opts.topic, pageSize]);\n\n /** Loads earlier messages until the one with this seq is held (a search result, a pinned message). True when it is. */\n const loadUntil = useCallback(\n async (seq: number) => {\n let current = queryClient.getQueryData<MessagesData>(messagesKey);\n for (let guard = 0; guard < 20 && current; guard++) {\n const first = current.messages[0];\n if (!first || first.seq <= seq || !current.hasEarlier) break;\n const page = await client.messages(id, {\n beforeSeq: first.seq,\n limit: Math.min(200, first.seq - seq + 10),\n threadRootId: opts.threadRootId,\n topic: opts.topic,\n });\n merge(page.messages, page.hasEarlier);\n current = queryClient.getQueryData<MessagesData>(messagesKey);\n }\n return Boolean(current?.messages.some((m) => m.seq === seq));\n },\n [client, id, merge, messagesKey, opts.threadRootId, opts.topic, queryClient],\n );\n\n const pending = useMemo(\n () =>\n queue.filter(\n (q) =>\n q.input.conversationId === id &&\n (q.input.threadRootId ?? null) === (opts.threadRootId ?? null) &&\n (!opts.topic || q.input.topic === opts.topic),\n ).map((q) => ({ ...q, files: q.pendingFiles })),\n [queue, id, opts.threadRootId, opts.topic],\n );\n const items: ThreadItem[] = useMemo(() => mergeThread(list, pending), [list, pending]);\n\n const signalTyping = useMemo(() => throttle(() => channel.current?.signal(\"typing\")), []);\n const signalRecording = useMemo(\n () => throttle(() => channel.current?.signal(\"recording-voice\")),\n [],\n );\n const signalRecordingVideo = useMemo(() => throttle(() => channel.current?.signal(\"recording-video\")), []);\n\n /** Sends text and files to this conversation (through the outbox: shown at once, retried safely). */\n const send = useCallback(\n (input: Omit<SendInput, \"conversationId\" | \"clientId\">, files: LocalFile[] = []) =>\n outbox.send({ ...input, conversationId: id, threadRootId: input.threadRootId ?? opts.threadRootId, topic: input.topic ?? opts.topic }, files),\n [outbox, id, opts.threadRootId, opts.topic],\n );\n\n return {\n conversation: conversation.data ?? null,\n members: members.data ?? [],\n role: access.data?.role ?? null,\n canPost: access.data?.canPost ?? false,\n /** Your role and posting rights are still loading: show the composer as waiting, not refused. */\n accessLoading: enabled && access.isLoading,\n /** The conversation itself is still loading (it settles to `null` when you can't see it). */\n conversationLoading: conversation.isLoading,\n /** Your role couldn't be loaded (offline): offer `refetchAccess`, don't say \"can't post\". */\n accessError: access.error ?? null,\n refetchAccess: access.refetch,\n /** The kind's rules (max members, threads, who posts). */\n kind: kindRules.data ?? null,\n /** May you pin here: owners and admins, or anyone in a two-person conversation. */\n canPin:\n access.data?.role === \"owner\" ||\n access.data?.role === \"admin\" ||\n (access.data?.role === \"member\" && kindRules.data?.maxMembers === 2),\n /** Gives a message whose files were lost on a relaunch its files again. */\n reattach: outbox.reattach,\n items,\n messages: list,\n isLoading: messages.isLoading || conversation.isLoading,\n error: messages.error ?? conversation.error,\n hasEarlier,\n loadingEarlier,\n loadEarlier,\n loadUntil,\n /** Where to draw the \"New\" line, from what you had read when it opened. */\n firstUnreadId: readWhenOpened === null ? null : firstUnreadId(list, readWhenOpened, userId),\n activity: currentActivity(activity),\n signalTyping,\n signalRecording,\n /** Tells the others you are recording a video (throttled). */\n signalRecordingVideo,\n markRead,\n send,\n retry: outbox.retry,\n discard: outbox.discard,\n refetch: catchUp,\n };\n}\n\nexport type ConversationState = ReturnType<typeof useConversation>;\n\n/** Edit, delete, react, pin, save and report, refreshing what they change. */\nexport function useMessageActions() {\n const { client } = useChat();\n const queryClient = useQueryClient();\n return useMemo(() => {\n const refresh = async (conversationId: string, messageId: string) => {\n const m = await client.message(messageId);\n queryClient.setQueriesData<MessagesData>({ queryKey: chatKeys.conversation(conversationId) }, (prev) =>\n m ? (upsertInto(prev, [m]) ?? prev) : prev,\n );\n };\n return {\n edit: async (m: ChatMessage, body: string, mentions: string[] = []) => {\n await client.edit(m.id, body, mentions);\n await refresh(m.conversationId, m.id);\n },\n remove: async (m: ChatMessage) => {\n await client.remove(m.id);\n await refresh(m.conversationId, m.id);\n },\n react: async (m: ChatMessage, emoji: string, on: boolean) => {\n await client.react(m.id, emoji, on);\n await refresh(m.conversationId, m.id);\n },\n pin: async (m: ChatMessage, on: boolean) => {\n await client.pin(m.id, on);\n void queryClient.invalidateQueries({ queryKey: chatKeys.pins(m.conversationId) });\n },\n save: (m: ChatMessage, on: boolean, note?: string) => client.save(m.id, on, note),\n report: (m: ChatMessage, reason: \"spam\" | \"harassment\" | \"safety\" | \"other\", note?: string) => client.report(m.id, reason, note),\n };\n }, [client, queryClient]);\n}\n\n/**\n * Names and avatars for these people, from the provider's `loadPeople`, fetched as one\n * batch per set of ids and cached. Unknown ids are left out.\n */\nexport function usePeople(userIds: (string | null | undefined)[]): Record<string, ChatPerson> {\n const { loadPeople } = useChat();\n const ids = useMemo(() => [...new Set(userIds.filter((x): x is string => Boolean(x)))].sort(), [userIds]);\n return (\n useQuery({\n queryKey: [\"genesis-chat\", \"people\", ids],\n queryFn: () => loadPeople!(ids),\n enabled: Boolean(loadPeople) && ids.length > 0,\n staleTime: 5 * 60_000,\n }).data ?? {}\n );\n}\n\n/** One person's name and avatar (see usePeople). */\nexport function usePerson(userId: string | null | undefined): ChatPerson | null {\n const people = usePeople(useMemo(() => [userId], [userId]));\n return userId ? (people[userId] ?? null) : null;\n}\n\n/** The pinned message ids of a conversation. */\nexport function usePins(conversationId: string | null | undefined): string[] {\n const { client } = useChat();\n return (\n useQuery({ queryKey: chatKeys.pins(conversationId ?? \"\"), queryFn: () => client.pins(conversationId!), enabled: Boolean(conversationId) }).data ?? []\n );\n}\n\n/** Signed links for files (cached by the client until shortly before they expire). */\nexport function useFileUrls(paths: (string | null | undefined)[]): Record<string, string> {\n const { client } = useChat();\n const list = useMemo(() => [...new Set(paths.filter((p): p is string => Boolean(p)))], [paths]);\n return (\n useQuery({ queryKey: chatKeys.files(list), queryFn: () => client.fileUrls(list), enabled: list.length > 0, staleTime: 50 * 60_000 }).data ?? {}\n );\n}\n\n/** Starts (or finds again) a conversation, then refreshes the inbox. */\nexport function useStartConversation() {\n const { client } = useChat();\n const queryClient = useQueryClient();\n return useCallback(\n async (kind: string, memberIds: string[], opts?: { title?: string; subjectType?: string; subjectId?: string }) => {\n const id = await client.startConversation(kind, memberIds, opts);\n void queryClient.invalidateQueries({ queryKey: [\"genesis-chat\", \"inbox\"] });\n return id;\n },\n [client, queryClient],\n );\n}\n\n// ── Migration 0002: topics, scheduled send, availability, broadcast, search, previews ──\n\n/**\n * A conversation's topics, live (set_topic's hint refreshes them), and `setTopic` for owners\n * and admins: add one (`setTopic(\"week-1\", { title: \"Week 1\" })`), close or reopen it.\n */\nexport function useTopics(conversationId: string | null | undefined) {\n const { client } = useChat();\n const queryClient = useQueryClient();\n const id = conversationId ?? \"\";\n const query = useQuery({ queryKey: chatKeys.topics(id), queryFn: () => client.topics(id), enabled: Boolean(conversationId), staleTime: 60_000 });\n const setTopic = useCallback(\n async (topic: string, input: TopicInput = {}) => {\n await client.setTopic(id, topic, input);\n await queryClient.invalidateQueries({ queryKey: chatKeys.topics(id) });\n },\n [client, id, queryClient],\n );\n const topics: ChatTopic[] = query.data ?? [];\n return { topics, isLoading: query.isLoading, error: query.error, setTopic, refetch: query.refetch };\n}\n\n/**\n * Your scheduled messages (in one conversation, or all), soonest first, with `schedule` and\n * `cancel`. `schedule` uploads byte files first (`LocalFile.data` a Blob or ArrayBuffer; a\n * native URI goes through the provider's `readFile`), then schedules them with the message.\n */\nexport function useScheduled(conversationId?: string | null, opts: { enabled?: boolean } = {}) {\n const { client, userId } = useChat();\n const queryClient = useQueryClient();\n const query = useQuery({\n queryKey: chatKeys.scheduled(conversationId),\n queryFn: () => client.scheduled(conversationId ?? undefined),\n enabled: opts.enabled ?? true,\n staleTime: 30_000,\n });\n const refresh = useCallback(() => queryClient.invalidateQueries({ queryKey: [\"genesis-chat\", \"scheduled\"] }), [queryClient]);\n const schedule = useCallback(\n async (input: Omit<ScheduleInput, \"clientId\"> & { clientId?: string }, files: LocalFile[] = []) => {\n const attachments = [...(input.attachments ?? [])];\n for (const f of files) {\n if (typeof f.data === \"string\") throw new Error(\"Scheduled files must be bytes (a Blob or ArrayBuffer)\");\n const path = await client.upload(input.conversationId, userId, f.data, { name: f.name, contentType: f.contentType });\n attachments.push({ ...f.meta, kind: f.meta?.kind ?? attachmentKindFor(f.contentType), path, name: f.meta?.name ?? f.name, mime: f.contentType });\n }\n const id = await client.schedule({ ...input, clientId: input.clientId ?? randomId(), attachments });\n await refresh();\n return id;\n },\n [client, refresh, userId],\n );\n const cancel = useCallback(\n async (id: string) => {\n queryClient.setQueriesData<ScheduledMessage[]>({ queryKey: [\"genesis-chat\", \"scheduled\"] }, (prev) => (Array.isArray(prev) ? prev.filter((s) => s.id !== id) : prev));\n try {\n await client.cancelScheduled(id);\n } finally {\n await refresh();\n }\n },\n [client, queryClient, refresh],\n );\n const items: ScheduledMessage[] = query.data ?? [];\n return { items, isLoading: query.isLoading, error: query.error, schedule, cancel, refetch: query.refetch };\n}\n\n/**\n * Availability: yours (`mine`, `setMine`) and, given a conversation, its members' (`members`,\n * `forUser`). Pair with `awayLine` and `isWithinWorkingHours` from the core.\n */\nexport function useAvailability(conversationId?: string | null) {\n const { client, userId } = useChat();\n const queryClient = useQueryClient();\n const mine = useQuery({ queryKey: chatKeys.myAvailability(), queryFn: () => client.myAvailability(), staleTime: 5 * 60_000 });\n const members = useQuery({\n queryKey: chatKeys.availability(conversationId ?? \"\"),\n queryFn: () => client.availabilityFor(conversationId!),\n enabled: Boolean(conversationId),\n staleTime: 5 * 60_000,\n });\n const setMine = useCallback(\n async (input: AvailabilityInput) => {\n await client.setAvailability(input);\n await queryClient.invalidateQueries({ queryKey: chatKeys.myAvailability() });\n await queryClient.invalidateQueries({ predicate: (q) => q.queryKey[0] === \"genesis-chat\" && q.queryKey[3] === \"availability\" });\n },\n [client, queryClient],\n );\n const list: ChatAvailability[] = members.data ?? [];\n const forUser = useCallback((id: string) => list.find((a) => a.userId === id) ?? null, [list]);\n return {\n mine: mine.data ?? null,\n mineLoading: mine.isLoading,\n setMine,\n members: list,\n /** Everyone else's, without yours. */\n others: list.filter((a) => a.userId !== userId),\n forUser,\n isLoading: mine.isLoading || (Boolean(conversationId) && members.isLoading),\n };\n}\n\n/** Sends one message into many conversations (replies come back privately in each). */\nexport function useBroadcast() {\n const { client } = useChat();\n const queryClient = useQueryClient();\n const [sending, setSending] = useState(false);\n const send = useCallback(\n async (conversationIds: string[], body: string, opts: { silent?: boolean; clientId?: string } = {}) => {\n setSending(true);\n try {\n const ids = await client.broadcast(conversationIds, opts.clientId ?? randomId(), body, { silent: opts.silent });\n void queryClient.invalidateQueries({ queryKey: [\"genesis-chat\", \"inbox\"] });\n return ids;\n } finally {\n setSending(false);\n }\n },\n [client, queryClient],\n );\n return { send, sending };\n}\n\n/** The value, once it has stopped changing for `ms`. */\nexport function useDebouncedValue<T>(value: T, ms: number): T {\n const [v, setV] = useState(value);\n useEffect(() => {\n if (ms <= 0) {\n setV(value);\n return;\n }\n const t = setTimeout(() => setV(value), ms);\n return () => clearTimeout(t);\n }, [value, ms]);\n return ms <= 0 ? value : v;\n}\n\n/** Search, debounced (250 ms by default), in one conversation or all of yours. Queries under two characters return nothing. */\nexport function useSearch(query: string, opts: { conversationId?: string | null; debounceMs?: number; limit?: number } = {}) {\n const { client } = useChat();\n const q = useDebouncedValue(query.trim(), opts.debounceMs ?? 250);\n const enabled = q.length >= 2;\n const result = useQuery({\n queryKey: chatKeys.search(q, opts.conversationId),\n queryFn: () => client.search(q, opts.conversationId ?? null, { limit: opts.limit }),\n enabled,\n staleTime: 30_000,\n });\n const hits: ChatSearchHit[] = enabled ? (result.data ?? []) : [];\n return {\n hits,\n /** The query the hits are for. */\n query: q,\n /** Typing, or fetching: results are on their way. */\n isSearching: query.trim() !== q || (enabled && result.isFetching),\n error: result.error,\n };\n}\n\n/** At most this many link-preview reads at once, per client. */\nexport const LINK_PREVIEW_CONCURRENCY = 4;\nconst previewGates = new WeakMap<object, <T>(task: () => Promise<T>) => Promise<T>>();\n\n/** A gate that lets `max` tasks run at once and queues the rest. */\nfunction gate(max: number) {\n let active = 0;\n const waiting: (() => void)[] = [];\n return async <T,>(task: () => Promise<T>): Promise<T> => {\n if (active >= max) await new Promise<void>((r) => waiting.push(r));\n active += 1;\n try {\n return await task();\n } finally {\n active -= 1;\n waiting.shift()?.();\n }\n };\n}\n\n/**\n * Link previews for these messages, by message id, in link order (only those the server has\n * fetched). Pass the messages whose text has links (`extractLinks(body).length > 0`). One query\n * per message, so a new message asks only for its own, and at most LINK_PREVIEW_CONCURRENCY\n * run at once. One with none yet is asked again a few times, since the runner fills them\n * moments after it lands.\n */\nexport function useLinkPreviews(messageIds: (string | null | undefined)[]): Record<string, ChatLinkPreview[]> {\n const { client } = useChat();\n const list = useMemo(() => [...new Set(messageIds.filter((u): u is string => Boolean(u)))].sort(), [messageIds]);\n let run = previewGates.get(client);\n if (!run) {\n run = gate(LINK_PREVIEW_CONCURRENCY);\n previewGates.set(client, run);\n }\n const limited = run;\n const combine = useCallback(\n (results: { data?: ChatLinkPreview[] }[]) => {\n let out: Record<string, ChatLinkPreview[]> | null = null;\n results.forEach((r, i) => {\n if (r.data) (out ??= {})[list[i]!] = r.data;\n });\n return out ?? EMPTY_PREVIEWS;\n },\n [list],\n );\n return useQueries({\n queries: list.map((id) => ({\n queryKey: chatKeys.preview(id),\n queryFn: () => limited(async () => (await client.linkPreviewsFor([id]))[id] ?? []),\n staleTime: 60_000,\n refetchInterval: (q: { state: { data?: ChatLinkPreview[]; dataUpdateCount: number } }) =>\n !q.state.data?.length && q.state.dataUpdateCount < 4 ? 5_000 : false,\n })),\n combine,\n });\n}\nconst EMPTY_PREVIEWS: Record<string, ChatLinkPreview[]> = {};\n\nexport type { ThreadItem, LocalFile, QueuedMessage };\n"]}
@@ -0,0 +1,187 @@
1
+ // src/native/push.ts
2
+ var CHAT_PUSH_CATEGORY = "messages";
3
+ var CHAT_REPLY_ACTION = "reply";
4
+ var CHAT_MARK_READ_ACTION = "mark-read";
5
+ var chatPushCopy = {
6
+ title: "Messages",
7
+ description: "New messages in your conversations.",
8
+ reply: "Reply",
9
+ replySubmit: "Send",
10
+ replyPlaceholder: "Message",
11
+ markRead: "Mark read"
12
+ };
13
+ function chatPath(conversationId) {
14
+ return `/messages/${conversationId}`;
15
+ }
16
+ function chatPushCategory(copy = {}) {
17
+ const c = { ...chatPushCopy, ...copy };
18
+ return { id: CHAT_PUSH_CATEGORY, title: c.title, description: c.description, importance: "high" };
19
+ }
20
+ function chatNotificationActions(copy = {}) {
21
+ const c = { ...chatPushCopy, ...copy };
22
+ return [
23
+ {
24
+ identifier: CHAT_REPLY_ACTION,
25
+ buttonTitle: c.reply,
26
+ textInput: { submitButtonTitle: c.replySubmit, placeholder: c.replyPlaceholder },
27
+ options: { opensAppToForeground: false }
28
+ },
29
+ { identifier: CHAT_MARK_READ_ACTION, buttonTitle: c.markRead, options: { opensAppToForeground: false } }
30
+ ];
31
+ }
32
+ function registerChatNotificationActions(notifications, copy, onError) {
33
+ try {
34
+ if (typeof notifications?.setNotificationCategoryAsync !== "function") {
35
+ onError?.(new Error("chat push: setNotificationCategoryAsync is missing, so Reply and Mark read aren't on message notifications"));
36
+ return Promise.resolve(void 0);
37
+ }
38
+ return Promise.resolve(notifications.setNotificationCategoryAsync(CHAT_PUSH_CATEGORY, chatNotificationActions(copy))).catch((error) => {
39
+ onError?.(error);
40
+ });
41
+ } catch (error) {
42
+ onError?.(error);
43
+ return Promise.resolve(void 0);
44
+ }
45
+ }
46
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
47
+ function conversationIdFromData(data) {
48
+ if (!data) return null;
49
+ const direct = typeof data.conversationId === "string" ? data.conversationId : typeof data.conversation_id === "string" ? data.conversation_id : null;
50
+ if (direct && UUID.test(direct)) return direct.toLowerCase();
51
+ const url = typeof data.url === "string" ? data.url : null;
52
+ const m = url ? /^\/messages\/([0-9a-f-]{36})(?:[/?#]|$)/i.exec(url) : null;
53
+ return m && UUID.test(m[1]) ? m[1].toLowerCase() : null;
54
+ }
55
+ function replyClientId(notificationId) {
56
+ if (UUID.test(notificationId)) return notificationId.toLowerCase();
57
+ const words = [2166136261, 16777619, 84696351, 1867459131].map((seed) => {
58
+ let h = seed >>> 0;
59
+ for (let i = 0; i < notificationId.length; i++) {
60
+ h ^= notificationId.charCodeAt(i);
61
+ h = Math.imul(h, 16777619) >>> 0;
62
+ }
63
+ return h.toString(16).padStart(8, "0");
64
+ });
65
+ const hex = words.join("");
66
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-8${hex.slice(13, 16)}-${(parseInt(hex[16], 16) & 3 | 8).toString(16)}${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
67
+ }
68
+ function replyClientIdFor(notificationId, data, body) {
69
+ const messageId = typeof data?.messageId === "string" ? data.messageId : typeof data?.message_id === "string" ? data.message_id : "";
70
+ return replyClientId(`${notificationId}|m:${messageId.toLowerCase()}|t:${body}`);
71
+ }
72
+ var PUSH_REPLY_WAIT_MS = 15e3;
73
+ function failedReply(input, userId, error = "Not sent. Check your connection.") {
74
+ return {
75
+ clientId: input.clientId,
76
+ input: { conversationId: input.conversationId, clientId: input.clientId, body: input.body },
77
+ senderId: userId,
78
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
79
+ status: "failed",
80
+ error,
81
+ previews: {},
82
+ files: [],
83
+ pendingFiles: [],
84
+ attempts: 1
85
+ };
86
+ }
87
+ async function handleChatPushResponse(response, deps) {
88
+ const action = response.actionIdentifier;
89
+ if (action !== CHAT_REPLY_ACTION && action !== CHAT_MARK_READ_ACTION) return "ignored";
90
+ const data = response.notification.request.content.data ?? {};
91
+ const conversationId = conversationIdFromData(data);
92
+ if (!conversationId) return "ignored";
93
+ const notificationId = response.notification.request.identifier;
94
+ try {
95
+ let outcome;
96
+ if (action === CHAT_REPLY_ACTION) {
97
+ const body = (response.userText ?? "").trim();
98
+ if (!body) return "ignored";
99
+ const input = { conversationId, clientId: replyClientIdFor(notificationId, data, body), body };
100
+ outcome = await sendReply(input, deps);
101
+ if (outcome === "replied") await deps.client.markRead(conversationId).catch(() => void 0);
102
+ } else {
103
+ const seq = typeof data.seq === "number" ? data.seq : typeof data.seq === "string" ? Number(data.seq) : void 0;
104
+ await deps.client.markRead(conversationId, Number.isFinite(seq) ? seq : void 0);
105
+ outcome = "marked-read";
106
+ }
107
+ await deps.notifications?.dismissNotificationAsync?.(notificationId).catch(() => void 0);
108
+ return outcome;
109
+ } catch (error) {
110
+ deps.onError?.(error);
111
+ return "failed";
112
+ }
113
+ }
114
+ async function sendReply(input, deps) {
115
+ const liveOutbox = () => (typeof deps.outbox === "function" ? deps.outbox() : deps.outbox) ?? null;
116
+ const live = liveOutbox();
117
+ if (live) return queueLive(live, input, deps.waitMs);
118
+ try {
119
+ await deps.client.send(input);
120
+ return "replied";
121
+ } catch (error) {
122
+ const opened = liveOutbox();
123
+ if (opened) return queueLive(opened, input, 0);
124
+ if (!deps.store || !deps.userId) throw error;
125
+ deps.onError?.(error);
126
+ const queue = await deps.store.load();
127
+ if (!queue.some((q) => q.clientId === input.clientId)) await deps.store.save([...queue, failedReply(input, deps.userId)]);
128
+ return "queued";
129
+ }
130
+ }
131
+ async function queueLive(live, input, waitMs = PUSH_REPLY_WAIT_MS) {
132
+ await live.load?.();
133
+ const mine = () => live.pending(input.conversationId).find((p) => p.clientId === input.clientId);
134
+ if (!mine()) await live.send(input);
135
+ const until = Date.now() + waitMs;
136
+ for (; ; ) {
137
+ const p = mine();
138
+ if (!p) return "replied";
139
+ if (p.status === "failed" || Date.now() >= until) return "queued";
140
+ await new Promise((r) => setTimeout(r, 100));
141
+ }
142
+ }
143
+ function chatThreadMatch(conversationId) {
144
+ if (!conversationId) return null;
145
+ const id = conversationId.toLowerCase();
146
+ return (data) => conversationIdFromData(data) === id;
147
+ }
148
+ function httpsUrl(value) {
149
+ if (typeof value !== "string" || value.length > 1024) return null;
150
+ try {
151
+ const url = new URL(value);
152
+ return url.protocol === "https:" && url.hostname.length > 0 && !url.username && !url.password ? value : null;
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ function shortText(value) {
158
+ if (typeof value !== "string") return null;
159
+ const t = value.trim();
160
+ return t ? t.slice(0, 200) : null;
161
+ }
162
+ function chatPushPayload(input) {
163
+ const conversationId = input.conversationId.toLowerCase();
164
+ if (!UUID.test(conversationId)) throw new Error("chatPushPayload: conversationId must be a uuid");
165
+ const data = { url: chatPath(conversationId), conversationId, messageId: input.messageId };
166
+ if (typeof input.seq === "number" && Number.isFinite(input.seq)) data.seq = input.seq;
167
+ if (input.hidePreviews) {
168
+ return { categoryId: CHAT_PUSH_CATEGORY, threadId: conversationId, mutableContent: false, data };
169
+ }
170
+ const senderName = shortText(input.sender?.name);
171
+ const senderId = shortText(input.sender?.id);
172
+ if (senderName && senderId) {
173
+ data.senderId = senderId;
174
+ data.senderName = senderName;
175
+ const avatar = httpsUrl(input.sender?.avatarUrl);
176
+ if (avatar) data.senderAvatar = avatar;
177
+ }
178
+ const groupName = shortText(input.groupName);
179
+ if (groupName) data.groupName = groupName;
180
+ const image = httpsUrl(input.imageUrl);
181
+ if (image) data.image = image;
182
+ return { categoryId: CHAT_PUSH_CATEGORY, threadId: conversationId, mutableContent: true, data };
183
+ }
184
+
185
+ export { CHAT_MARK_READ_ACTION, CHAT_PUSH_CATEGORY, CHAT_REPLY_ACTION, PUSH_REPLY_WAIT_MS, chatNotificationActions, chatPath, chatPushCategory, chatPushCopy, chatPushPayload, chatThreadMatch, conversationIdFromData, failedReply, handleChatPushResponse, registerChatNotificationActions, replyClientId, replyClientIdFor };
186
+ //# sourceMappingURL=chunk-VEPZROQB.js.map
187
+ //# sourceMappingURL=chunk-VEPZROQB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/native/push.ts"],"names":[],"mappings":"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","file":"chunk-VEPZROQB.js","sourcesContent":["/**\n * @feat:chat\n * Push glue for genesis-native's push block, data only: the `messages`\n * category (a switch in Notification settings and an Android channel), its\n * notification actions (Reply with a text field, Mark read), what a tap opens\n * (`data.url` = `/messages/<conversationId>`), the reply and mark-read\n * handlers for the block's `onResponse`, and the match that keeps a thread's\n * own pushes out of the foreground while it is open (the block's `suppress`).\n *\n * const push = createPush({ …, categories: [...yours, chatPushCategory()],\n * onResponse: (r) => void handleChatPushResponse(r, { client: chatClient, notifications: Notifications }) });\n * void registerChatNotificationActions(Notifications); // once, at start\n * useSuppressNotifications(chatThreadMatch(conversationId)); // in the thread screen\n *\n * The sender sets `categoryId: \"messages\"` (iOS) / the `messages` channel\n * (Android) and `data: { url: \"/messages/<id>\", conversationId, seq, messageId }`;\n * `chatPushPayload` builds those and the fields iOS rich push reads (the\n * sender's name and photo, the group, a photo), and it is server safe: import\n * it from `@marktiderman/genesis-chat/push`.\n *\n * A reply goes through the outbox, so a phone that is offline keeps it: pass\n * the live one (`useChat().outbox`) when the chat screens are mounted, and the\n * persisted store and user id (`ChatProvider outboxStore`) for when they\n * aren't. A reply that can't be sent then waits there, failed, with Retry in\n * its thread.\n */\nimport type { ChatClient } from \"../core/client\";\nimport type { Outbox, OutboxStore, QueuedMessage } from \"../core/outbox\";\n\nexport const CHAT_PUSH_CATEGORY = \"messages\";\nexport const CHAT_REPLY_ACTION = \"reply\";\nexport const CHAT_MARK_READ_ACTION = \"mark-read\";\n\nexport interface ChatPushCopy {\n title: string;\n description: string;\n reply: string;\n replySubmit: string;\n replyPlaceholder: string;\n markRead: string;\n}\n\nexport const chatPushCopy: ChatPushCopy = {\n title: \"Messages\",\n description: \"New messages in your conversations.\",\n reply: \"Reply\",\n replySubmit: \"Send\",\n replyPlaceholder: \"Message\",\n markRead: \"Mark read\",\n};\n\n/** The path a message's push opens. */\nexport function chatPath(conversationId: string): string {\n return `/messages/${conversationId}`;\n}\n\n/** The category for createPush's `categories` (genesis-native's NotificationCategory). */\nexport function chatPushCategory(copy: Partial<ChatPushCopy> = {}) {\n const c = { ...chatPushCopy, ...copy };\n return { id: CHAT_PUSH_CATEGORY, title: c.title, description: c.description, importance: \"high\" as const };\n}\n\n/** expo-notifications' `setNotificationCategoryAsync`, the one call this needs. */\nexport interface NotificationCategoriesLike {\n setNotificationCategoryAsync(\n identifier: string,\n actions: {\n identifier: string;\n buttonTitle: string;\n textInput?: { submitButtonTitle: string; placeholder: string };\n options?: { opensAppToForeground?: boolean; isDestructive?: boolean; isAuthenticationRequired?: boolean };\n }[],\n options?: Record<string, unknown>,\n ): Promise<unknown>;\n dismissNotificationAsync?(identifier: string): Promise<void>;\n}\n\n/** The actions on a message's notification: Reply (a text field) and Mark read, neither opening the app. */\nexport function chatNotificationActions(copy: Partial<ChatPushCopy> = {}) {\n const c = { ...chatPushCopy, ...copy };\n return [\n {\n identifier: CHAT_REPLY_ACTION,\n buttonTitle: c.reply,\n textInput: { submitButtonTitle: c.replySubmit, placeholder: c.replyPlaceholder },\n options: { opensAppToForeground: false },\n },\n { identifier: CHAT_MARK_READ_ACTION, buttonTitle: c.markRead, options: { opensAppToForeground: false } },\n ];\n}\n\n/** Registers the actions with the OS (once, at start; it is idempotent). */\nexport function registerChatNotificationActions(\n notifications: NotificationCategoriesLike,\n copy?: Partial<ChatPushCopy>,\n onError?: (error: unknown) => void,\n): Promise<unknown> {\n // Never throws, at module load least of all: a module without categories (web, a test's mock) is reported.\n try {\n if (typeof notifications?.setNotificationCategoryAsync !== \"function\") {\n onError?.(new Error(\"chat push: setNotificationCategoryAsync is missing, so Reply and Mark read aren't on message notifications\"));\n return Promise.resolve(undefined);\n }\n return Promise.resolve(notifications.setNotificationCategoryAsync(CHAT_PUSH_CATEGORY, chatNotificationActions(copy))).catch((error: unknown) => {\n onError?.(error);\n });\n } catch (error) {\n onError?.(error);\n return Promise.resolve(undefined);\n }\n}\n\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** The conversation a push is about: `data.conversationId`, else the id in `data.url`. */\nexport function conversationIdFromData(data: Record<string, unknown> | null | undefined): string | null {\n if (!data) return null;\n const direct = typeof data.conversationId === \"string\" ? data.conversationId : typeof data.conversation_id === \"string\" ? data.conversation_id : null;\n if (direct && UUID.test(direct)) return direct.toLowerCase();\n const url = typeof data.url === \"string\" ? data.url : null;\n const m = url ? /^\\/messages\\/([0-9a-f-]{36})(?:[/?#]|$)/i.exec(url) : null;\n return m && UUID.test(m[1]!) ? m[1]!.toLowerCase() : null;\n}\n\n/**\n * The client id a reply from this notification sends with: the notification's\n * own id, as a uuid (it already is one on iOS; anything else is hashed into\n * one), so the same reply delivered twice posts once.\n */\nexport function replyClientId(notificationId: string): string {\n if (UUID.test(notificationId)) return notificationId.toLowerCase();\n // Four 32-bit FNV-1a hashes with different seeds: 128 stable bits.\n const words = [0x811c9dc5, 0x01000193, 0x050c5d1f, 0x6f4f2a3b].map((seed) => {\n let h = seed >>> 0;\n for (let i = 0; i < notificationId.length; i++) {\n h ^= notificationId.charCodeAt(i);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return h.toString(16).padStart(8, \"0\");\n });\n const hex = words.join(\"\");\n // Version 8 (custom) and the RFC 4122 variant, so it is a valid uuid.\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-8${hex.slice(13, 16)}-${((parseInt(hex[16]!, 16) & 0x3) | 0x8).toString(16)}${hex.slice(17, 20)}-${hex.slice(20, 32)}`;\n}\n\n/**\n * The client id for one reply: from the notification's id, the message it is\n * about (`data.messageId`) and the reply's text. The same reply delivered\n * twice posts once; two replies to one conversation never share an id, even\n * when iOS reuses the notification's id for a collapsed push.\n */\nexport function replyClientIdFor(notificationId: string, data: Record<string, unknown> | null | undefined, body: string): string {\n const messageId = typeof data?.messageId === \"string\" ? data.messageId : typeof data?.message_id === \"string\" ? data.message_id : \"\";\n return replyClientId(`${notificationId}|m:${messageId.toLowerCase()}|t:${body}`);\n}\n\n/** The part of a notification response this reads (expo-notifications' `NotificationResponse`). */\nexport interface ChatPushResponseLike {\n actionIdentifier: string;\n /** The text typed into Reply. */\n userText?: string;\n notification: { request: { identifier: string; content: { data?: Record<string, unknown> | null } } };\n}\n\n/** \"queued\": the reply couldn't go now and waits in the outbox, failed, for Retry. */\nexport type ChatPushOutcome = \"replied\" | \"queued\" | \"marked-read\" | \"ignored\" | \"failed\";\n\ntype LiveOutbox = Pick<Outbox, \"send\" | \"pending\"> & Partial<Pick<Outbox, \"load\">>;\n\n/** How long a reply queued in the live outbox is waited for before it counts as queued, not sent. */\nexport const PUSH_REPLY_WAIT_MS = 15_000;\n\n/** Where a reply waits when it can't go now. */\nexport interface ChatPushOutboxDeps {\n /** The live outbox (`useChat().outbox`), or a getter for it (read again after a slow failure): the reply is queued there and sent in order. */\n outbox?: LiveOutbox | null | (() => LiveOutbox | null | undefined);\n /** Without a live outbox: the persisted store (`ChatProvider outboxStore`) a reply that failed is added to. */\n store?: OutboxStore | null;\n /** Whose store it is (the signed-in user's id). */\n userId?: string | null;\n}\n\n/** A reply that couldn't be sent, as the outbox keeps it: shown failed in its thread, with Retry. */\nexport function failedReply(input: { conversationId: string; clientId: string; body: string }, userId: string, error = \"Not sent. Check your connection.\"): QueuedMessage {\n return {\n clientId: input.clientId,\n input: { conversationId: input.conversationId, clientId: input.clientId, body: input.body },\n senderId: userId,\n createdAt: new Date().toISOString(),\n status: \"failed\",\n error,\n previews: {},\n files: [],\n pendingFiles: [],\n attempts: 1,\n };\n}\n\n/**\n * Handles Reply and Mark read from a message's notification (pass it as the\n * push block's `onResponse`). Taps on the notification itself are the block's\n * routing, not this. Never throws: a failure is the outcome \"failed\" (and\n * `onError`).\n */\nexport async function handleChatPushResponse(\n response: ChatPushResponseLike,\n deps: {\n client: Pick<ChatClient, \"send\" | \"markRead\">;\n notifications?: Pick<NotificationCategoriesLike, \"dismissNotificationAsync\">;\n onError?: (error: unknown) => void;\n /** How long to wait for a reply queued in the live outbox to go. Default PUSH_REPLY_WAIT_MS. */\n waitMs?: number;\n } & ChatPushOutboxDeps,\n): Promise<ChatPushOutcome> {\n const action = response.actionIdentifier;\n if (action !== CHAT_REPLY_ACTION && action !== CHAT_MARK_READ_ACTION) return \"ignored\";\n const data = response.notification.request.content.data ?? {};\n const conversationId = conversationIdFromData(data);\n if (!conversationId) return \"ignored\";\n const notificationId = response.notification.request.identifier;\n try {\n let outcome: ChatPushOutcome;\n if (action === CHAT_REPLY_ACTION) {\n const body = (response.userText ?? \"\").trim();\n if (!body) return \"ignored\";\n const input = { conversationId, clientId: replyClientIdFor(notificationId, data, body), body };\n outcome = await sendReply(input, deps);\n // A reply reads the conversation too, as answering in the app would.\n if (outcome === \"replied\") await deps.client.markRead(conversationId).catch(() => undefined);\n } else {\n const seq = typeof data.seq === \"number\" ? data.seq : typeof data.seq === \"string\" ? Number(data.seq) : undefined;\n await deps.client.markRead(conversationId, Number.isFinite(seq) ? seq : undefined);\n outcome = \"marked-read\";\n }\n await deps.notifications?.dismissNotificationAsync?.(notificationId).catch(() => undefined);\n return outcome;\n } catch (error) {\n deps.onError?.(error);\n return \"failed\";\n }\n}\n\nasync function sendReply(\n input: { conversationId: string; clientId: string; body: string },\n deps: { client: Pick<ChatClient, \"send\">; onError?: (error: unknown) => void; waitMs?: number } & ChatPushOutboxDeps,\n): Promise<ChatPushOutcome> {\n const liveOutbox = () => (typeof deps.outbox === \"function\" ? deps.outbox() : deps.outbox) ?? null;\n const live = liveOutbox();\n if (live) return queueLive(live, input, deps.waitMs);\n try {\n await deps.client.send(input);\n return \"replied\";\n } catch (error) {\n // The chat screens may have opened while the send failed: their outbox owns the saved copy now.\n const opened = liveOutbox();\n if (opened) return queueLive(opened, input, 0);\n if (!deps.store || !deps.userId) throw error;\n deps.onError?.(error);\n const queue = await deps.store.load();\n if (!queue.some((q) => q.clientId === input.clientId)) await deps.store.save([...queue, failedReply(input, deps.userId)]);\n return \"queued\";\n }\n}\n\n/**\n * Queues the reply in the live outbox (once) and waits, briefly, for it to go: \"replied\" once it\n * has left the queue, \"queued\" while it is still there or failed (it waits there for Retry).\n */\nasync function queueLive(live: LiveOutbox, input: { conversationId: string; clientId: string; body: string }, waitMs = PUSH_REPLY_WAIT_MS): Promise<ChatPushOutcome> {\n await live.load?.();\n const mine = () => live.pending(input.conversationId).find((p) => p.clientId === input.clientId);\n if (!mine()) await live.send(input);\n const until = Date.now() + waitMs;\n for (;;) {\n const p = mine();\n if (!p) return \"replied\";\n if (p.status === \"failed\" || Date.now() >= until) return \"queued\";\n await new Promise((r) => setTimeout(r, 100));\n }\n}\n\n/** For the push block's `suppress` / `useSuppressNotifications`: this conversation's pushes, while its thread is open. */\nexport function chatThreadMatch(conversationId: string | null | undefined): ((data: Record<string, unknown>) => boolean) | null {\n if (!conversationId) return null;\n const id = conversationId.toLowerCase();\n return (data) => conversationIdFromData(data) === id;\n}\n\n/** What a message's push says about itself, for `chatPushPayload`. */\nexport interface ChatPushPayloadInput {\n conversationId: string;\n messageId: string;\n seq?: number | null | undefined;\n /** Who sent it: an opaque id (never an email or phone), the name to show and an https photo. */\n sender?: { id: string; name: string; avatarUrl?: string | null | undefined } | null | undefined;\n /** The conversation's name when it is a group; leave it out for a direct conversation. */\n groupName?: string | null | undefined;\n /** An https photo in the message, attached to the notification. */\n imageUrl?: string | null | undefined;\n /**\n * The recipient hides previews: the push carries the link and ids only (no\n * sender, group or photo) and the service extension doesn't run. Send neutral\n * title and body text with it.\n */\n hidePreviews?: boolean | undefined;\n}\n\n/** The `data` of a message's push. Flat strings and numbers, as genesis-notifications' `PushMessage.data` takes. */\nexport interface ChatPushData {\n [key: string]: string | number;\n url: string;\n conversationId: string;\n messageId: string;\n}\n\n/** The fields to spread into an Expo push message (with `to`, `title`, `body`, `badge`, `channelId`). */\nexport interface ChatPushPayload {\n categoryId: typeof CHAT_PUSH_CATEGORY;\n /** Groups the conversation's notifications, and is the conversation iOS files a Communication Notification under. */\n threadId: string;\n /** iOS runs genesis-native's Notification Service Extension only when this is true (`mutable-content: 1`). */\n mutableContent: boolean;\n data: ChatPushData;\n}\n\n/** The extension fetches only https URLs (no credentials, at most 1024 characters): genesis-native's `isRichPushUrl`. */\nfunction httpsUrl(value: unknown): string | null {\n if (typeof value !== \"string\" || value.length > 1024) return null;\n try {\n const url = new URL(value);\n return url.protocol === \"https:\" && url.hostname.length > 0 && !url.username && !url.password ? value : null;\n } catch {\n return null;\n }\n}\n\nfunction shortText(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const t = value.trim();\n return t ? t.slice(0, 200) : null;\n}\n\n/**\n * The category, thread, `mutableContent` and `data` of a message's push, for\n * the server that sends it:\n *\n * const message = { to, title, body, badge, channelId: \"messages\", ...chatPushPayload({ conversationId, messageId, seq, sender, groupName, imageUrl, hidePreviews }) };\n *\n * On iOS, with the rich push plugin, a push with a sender is a Communication\n * Notification (the sender's photo; Focus treats it as a message) and `imageUrl`\n * is attached. Fields that aren't usable (a URL that isn't https, an empty\n * name) are left out rather than sent.\n */\nexport function chatPushPayload(input: ChatPushPayloadInput): ChatPushPayload {\n const conversationId = input.conversationId.toLowerCase();\n if (!UUID.test(conversationId)) throw new Error(\"chatPushPayload: conversationId must be a uuid\");\n const data: ChatPushData = { url: chatPath(conversationId), conversationId, messageId: input.messageId };\n if (typeof input.seq === \"number\" && Number.isFinite(input.seq)) data.seq = input.seq;\n if (input.hidePreviews) {\n return { categoryId: CHAT_PUSH_CATEGORY, threadId: conversationId, mutableContent: false, data };\n }\n const senderName = shortText(input.sender?.name);\n const senderId = shortText(input.sender?.id);\n if (senderName && senderId) {\n data.senderId = senderId;\n data.senderName = senderName;\n const avatar = httpsUrl(input.sender?.avatarUrl);\n if (avatar) data.senderAvatar = avatar;\n }\n const groupName = shortText(input.groupName);\n if (groupName) data.groupName = groupName;\n const image = httpsUrl(input.imageUrl);\n if (image) data.image = image;\n return { categoryId: CHAT_PUSH_CATEGORY, threadId: conversationId, mutableContent: true, data };\n}\n"]}