@agimon-ai/doompi-author 0.0.1-alpha.2 → 0.0.1-alpha.21

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 (318) hide show
  1. package/README.md +5 -1
  2. package/dist/apiContracts.cjs +316 -0
  3. package/dist/apiContracts.cjs.map +1 -0
  4. package/dist/apiContracts.d.mts +53 -0
  5. package/dist/apiContracts.d.mts.map +1 -0
  6. package/dist/apiContracts.mjs +311 -0
  7. package/dist/apiContracts.mjs.map +1 -0
  8. package/dist/authorCommand-B90ghX5J.mjs +226 -0
  9. package/dist/authorCommand-B90ghX5J.mjs.map +1 -0
  10. package/dist/authorCommand-Cd5dnLF7.cjs +315 -0
  11. package/dist/authorCommand-Cd5dnLF7.cjs.map +1 -0
  12. package/dist/authorFacade-B9-Re3Ga.mjs +75 -0
  13. package/dist/authorFacade-B9-Re3Ga.mjs.map +1 -0
  14. package/dist/authorFacade-BvCSzZ5K.cjs +104 -0
  15. package/dist/authorFacade-BvCSzZ5K.cjs.map +1 -0
  16. package/dist/authorFacade-RZ6EXlR5.d.mts +113 -0
  17. package/dist/authorFacade-RZ6EXlR5.d.mts.map +1 -0
  18. package/dist/authorFacade.cjs +26 -0
  19. package/dist/authorFacade.d.mts +3 -0
  20. package/dist/authorFacade.mjs +3 -0
  21. package/dist/extensions/pi.cjs +172 -1
  22. package/dist/extensions/pi.cjs.map +1 -0
  23. package/dist/extensions/pi.d.mts +6 -2
  24. package/dist/extensions/pi.d.mts.map +1 -0
  25. package/dist/extensions/pi.mjs +167 -1
  26. package/dist/extensions/pi.mjs.map +1 -0
  27. package/dist/extensions/server.cjs +659 -0
  28. package/dist/extensions/server.cjs.map +1 -0
  29. package/dist/extensions/server.d.mts +5 -0
  30. package/dist/extensions/server.d.mts.map +1 -0
  31. package/dist/extensions/server.mjs +652 -0
  32. package/dist/extensions/server.mjs.map +1 -0
  33. package/dist/extensions/web.mjs +4231 -0
  34. package/dist/extensions/web.mjs.map +1 -0
  35. package/dist/index-Btm_xNOb.d.mts +386 -0
  36. package/dist/index-Btm_xNOb.d.mts.map +1 -0
  37. package/dist/index.cjs +35 -1
  38. package/dist/index.d.mts +2 -21
  39. package/dist/index.mjs +4 -1
  40. package/dist/monitor-7T5YF6px.mjs +70 -0
  41. package/dist/monitor-7T5YF6px.mjs.map +1 -0
  42. package/dist/monitor-BxY8s_AX.cjs +81 -0
  43. package/dist/monitor-BxY8s_AX.cjs.map +1 -0
  44. package/dist/structuredDocuments-Bg6DIqu5.cjs +960 -0
  45. package/dist/structuredDocuments-Bg6DIqu5.cjs.map +1 -0
  46. package/dist/structuredDocuments-CzMYD0Bq.mjs +810 -0
  47. package/dist/structuredDocuments-CzMYD0Bq.mjs.map +1 -0
  48. package/package.json +48 -38
  49. package/src/types/authorPreview.ts +76 -0
  50. package/src/types/webAuthor.ts +1 -1
  51. package/dist/_virtual/_rolldown/runtime.cjs +0 -1
  52. package/dist/adapters/authorApi.cjs +0 -2
  53. package/dist/adapters/authorApi.cjs.map +0 -1
  54. package/dist/adapters/authorBridgeApi.cjs +0 -2
  55. package/dist/adapters/authorBridgeApi.cjs.map +0 -1
  56. package/dist/adapters/authorDocumentApi.cjs +0 -2
  57. package/dist/adapters/authorDocumentApi.cjs.map +0 -1
  58. package/dist/adapters/pi/authorBridgeClient.cjs +0 -2
  59. package/dist/adapters/pi/authorBridgeClient.cjs.map +0 -1
  60. package/dist/adapters/pi/authorTools.cjs +0 -2
  61. package/dist/adapters/pi/authorTools.cjs.map +0 -1
  62. package/dist/adapters/pi/extension.cjs +0 -2
  63. package/dist/adapters/pi/extension.cjs.map +0 -1
  64. package/dist/adapters/structuredDocuments/common.cjs +0 -2
  65. package/dist/adapters/structuredDocuments/common.cjs.map +0 -1
  66. package/dist/adapters/structuredDocuments/csv.cjs +0 -7
  67. package/dist/adapters/structuredDocuments/csv.cjs.map +0 -1
  68. package/dist/adapters/structuredDocuments/index.cjs +0 -2
  69. package/dist/adapters/structuredDocuments/index.cjs.map +0 -1
  70. package/dist/adapters/structuredDocuments/markdownSlides.cjs +0 -6
  71. package/dist/adapters/structuredDocuments/markdownSlides.cjs.map +0 -1
  72. package/dist/adapters/structuredDocuments/ooxmlArchive.cjs +0 -2
  73. package/dist/adapters/structuredDocuments/ooxmlArchive.cjs.map +0 -1
  74. package/dist/adapters/structuredDocuments/pptx.cjs +0 -2
  75. package/dist/adapters/structuredDocuments/pptx.cjs.map +0 -1
  76. package/dist/adapters/structuredDocuments/xlsx.cjs +0 -2
  77. package/dist/adapters/structuredDocuments/xlsx.cjs.map +0 -1
  78. package/dist/adapters/structuredDocuments/xmlText.cjs +0 -2
  79. package/dist/adapters/structuredDocuments/xmlText.cjs.map +0 -1
  80. package/dist/adapters/webAuthorChannel.cjs +0 -2
  81. package/dist/adapters/webAuthorChannel.cjs.map +0 -1
  82. package/dist/commands/doomAuthorCommand.cjs +0 -2
  83. package/dist/commands/doomAuthorCommand.cjs.map +0 -1
  84. package/dist/container/index.cjs +0 -2
  85. package/dist/container/index.cjs.map +0 -1
  86. package/dist/extensions/pi.d.cts +0 -2
  87. package/dist/index.d.cts +0 -21
  88. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.cts +0 -6
  89. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.cts.map +0 -1
  90. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.mts +0 -6
  91. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.mts.map +0 -1
  92. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.cts +0 -7
  93. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.cts.map +0 -1
  94. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.mts +0 -7
  95. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.mts.map +0 -1
  96. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.cts +0 -12
  97. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.cts.map +0 -1
  98. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.mts +0 -12
  99. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.mts.map +0 -1
  100. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.cts +0 -12
  101. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.cts.map +0 -1
  102. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.mts +0 -12
  103. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.mts.map +0 -1
  104. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.cts +0 -9
  105. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.cts.map +0 -1
  106. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.mts +0 -9
  107. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.mts.map +0 -1
  108. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.cts +0 -4
  109. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.cts.map +0 -1
  110. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.mts +0 -4
  111. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.mts.map +0 -1
  112. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.cts +0 -9
  113. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.cts.map +0 -1
  114. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.mts +0 -9
  115. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.mts.map +0 -1
  116. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.cts +0 -8
  117. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.cts.map +0 -1
  118. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.mts +0 -8
  119. package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.mts.map +0 -1
  120. package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.cts +0 -15
  121. package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.cts.map +0 -1
  122. package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.mts +0 -15
  123. package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.mts.map +0 -1
  124. package/dist/packages/minor/doompi-author/src/adapters/authorApi.mjs +0 -2
  125. package/dist/packages/minor/doompi-author/src/adapters/authorApi.mjs.map +0 -1
  126. package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.cts +0 -6
  127. package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.cts.map +0 -1
  128. package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.mts +0 -6
  129. package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.mts.map +0 -1
  130. package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.mjs +0 -2
  131. package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.mjs.map +0 -1
  132. package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.cts +0 -11
  133. package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.cts.map +0 -1
  134. package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.mts +0 -11
  135. package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.mts.map +0 -1
  136. package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.mjs +0 -2
  137. package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.mjs.map +0 -1
  138. package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.cts +0 -18
  139. package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.cts.map +0 -1
  140. package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.mts +0 -18
  141. package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.mts.map +0 -1
  142. package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.mjs +0 -2
  143. package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.mjs.map +0 -1
  144. package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.cts +0 -9
  145. package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.cts.map +0 -1
  146. package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.mts +0 -9
  147. package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.mts.map +0 -1
  148. package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.mjs +0 -2
  149. package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.mjs.map +0 -1
  150. package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.cts +0 -19
  151. package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.cts.map +0 -1
  152. package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.mts +0 -19
  153. package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.mts.map +0 -1
  154. package/dist/packages/minor/doompi-author/src/adapters/pi/extension.mjs +0 -2
  155. package/dist/packages/minor/doompi-author/src/adapters/pi/extension.mjs.map +0 -1
  156. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/common.mjs +0 -2
  157. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/common.mjs.map +0 -1
  158. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.cts +0 -7
  159. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.cts.map +0 -1
  160. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.mts +0 -7
  161. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.mts.map +0 -1
  162. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.mjs +0 -7
  163. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.mjs.map +0 -1
  164. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.cts +0 -13
  165. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.cts.map +0 -1
  166. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.mts +0 -13
  167. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.mts.map +0 -1
  168. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.mjs +0 -2
  169. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.mjs.map +0 -1
  170. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.cts +0 -7
  171. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.cts.map +0 -1
  172. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.mts +0 -7
  173. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.mts.map +0 -1
  174. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.mjs +0 -6
  175. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.mjs.map +0 -1
  176. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.cts +0 -13
  177. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.cts.map +0 -1
  178. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.mts +0 -13
  179. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.mts.map +0 -1
  180. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.mjs +0 -2
  181. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.mjs.map +0 -1
  182. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.cts +0 -7
  183. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.cts.map +0 -1
  184. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.mts +0 -7
  185. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.mts.map +0 -1
  186. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.mjs +0 -2
  187. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.mjs.map +0 -1
  188. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.cts +0 -7
  189. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.cts.map +0 -1
  190. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.mts +0 -7
  191. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.mts.map +0 -1
  192. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.mjs +0 -2
  193. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.mjs.map +0 -1
  194. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xmlText.mjs +0 -2
  195. package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xmlText.mjs.map +0 -1
  196. package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.cts +0 -6
  197. package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.cts.map +0 -1
  198. package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.mts +0 -6
  199. package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.mts.map +0 -1
  200. package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.mjs +0 -2
  201. package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.mjs.map +0 -1
  202. package/dist/packages/minor/doompi-author/src/commands/doomAuthorCommand.mjs +0 -2
  203. package/dist/packages/minor/doompi-author/src/commands/doomAuthorCommand.mjs.map +0 -1
  204. package/dist/packages/minor/doompi-author/src/container/index.d.cts +0 -5
  205. package/dist/packages/minor/doompi-author/src/container/index.d.cts.map +0 -1
  206. package/dist/packages/minor/doompi-author/src/container/index.d.mts +0 -5
  207. package/dist/packages/minor/doompi-author/src/container/index.d.mts.map +0 -1
  208. package/dist/packages/minor/doompi-author/src/container/index.mjs +0 -2
  209. package/dist/packages/minor/doompi-author/src/container/index.mjs.map +0 -1
  210. package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.cts +0 -16
  211. package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.cts.map +0 -1
  212. package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.mts +0 -16
  213. package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.mts.map +0 -1
  214. package/dist/packages/minor/doompi-author/src/schemas/authorTools.mjs +0 -2
  215. package/dist/packages/minor/doompi-author/src/schemas/authorTools.mjs.map +0 -1
  216. package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.cts +0 -31
  217. package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.cts.map +0 -1
  218. package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.mts +0 -31
  219. package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.mts.map +0 -1
  220. package/dist/packages/minor/doompi-author/src/services/authorBridgeState.mjs +0 -2
  221. package/dist/packages/minor/doompi-author/src/services/authorBridgeState.mjs.map +0 -1
  222. package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.cts +0 -10
  223. package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.cts.map +0 -1
  224. package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.mts +0 -10
  225. package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.mts.map +0 -1
  226. package/dist/packages/minor/doompi-author/src/services/authorMode.d.cts +0 -19
  227. package/dist/packages/minor/doompi-author/src/services/authorMode.d.cts.map +0 -1
  228. package/dist/packages/minor/doompi-author/src/services/authorMode.d.mts +0 -19
  229. package/dist/packages/minor/doompi-author/src/services/authorMode.d.mts.map +0 -1
  230. package/dist/packages/minor/doompi-author/src/services/authorMode.mjs +0 -3
  231. package/dist/packages/minor/doompi-author/src/services/authorMode.mjs.map +0 -1
  232. package/dist/packages/minor/doompi-author/src/services/extensionService.d.cts +0 -7
  233. package/dist/packages/minor/doompi-author/src/services/extensionService.d.cts.map +0 -1
  234. package/dist/packages/minor/doompi-author/src/services/extensionService.d.mts +0 -7
  235. package/dist/packages/minor/doompi-author/src/services/extensionService.d.mts.map +0 -1
  236. package/dist/packages/minor/doompi-author/src/services/extensionService.mjs +0 -2
  237. package/dist/packages/minor/doompi-author/src/services/extensionService.mjs.map +0 -1
  238. package/dist/packages/minor/doompi-author/src/types/author.d.cts +0 -39
  239. package/dist/packages/minor/doompi-author/src/types/author.d.cts.map +0 -1
  240. package/dist/packages/minor/doompi-author/src/types/author.d.mts +0 -39
  241. package/dist/packages/minor/doompi-author/src/types/author.d.mts.map +0 -1
  242. package/dist/packages/minor/doompi-author/src/types/author.mjs +0 -2
  243. package/dist/packages/minor/doompi-author/src/types/author.mjs.map +0 -1
  244. package/dist/packages/minor/doompi-author/src/types/authorApi.d.cts +0 -23
  245. package/dist/packages/minor/doompi-author/src/types/authorApi.d.cts.map +0 -1
  246. package/dist/packages/minor/doompi-author/src/types/authorApi.d.mts +0 -23
  247. package/dist/packages/minor/doompi-author/src/types/authorApi.d.mts.map +0 -1
  248. package/dist/packages/minor/doompi-author/src/types/authorApi.mjs +0 -2
  249. package/dist/packages/minor/doompi-author/src/types/authorApi.mjs.map +0 -1
  250. package/dist/packages/minor/doompi-author/src/types/extension.d.cts +0 -16
  251. package/dist/packages/minor/doompi-author/src/types/extension.d.cts.map +0 -1
  252. package/dist/packages/minor/doompi-author/src/types/extension.d.mts +0 -16
  253. package/dist/packages/minor/doompi-author/src/types/extension.d.mts.map +0 -1
  254. package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.cts +0 -60
  255. package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.cts.map +0 -1
  256. package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.mts +0 -60
  257. package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.mts.map +0 -1
  258. package/dist/packages/minor/doompi-author/src/types/webAuthor.d.cts +0 -67
  259. package/dist/packages/minor/doompi-author/src/types/webAuthor.d.cts.map +0 -1
  260. package/dist/packages/minor/doompi-author/src/types/webAuthor.d.mts +0 -67
  261. package/dist/packages/minor/doompi-author/src/types/webAuthor.d.mts.map +0 -1
  262. package/dist/packages/minor/doompi-author/src/types/webAuthor.mjs +0 -2
  263. package/dist/packages/minor/doompi-author/src/types/webAuthor.mjs.map +0 -1
  264. package/dist/schemas/authorTools.cjs +0 -2
  265. package/dist/schemas/authorTools.cjs.map +0 -1
  266. package/dist/services/authorBridgeState.cjs +0 -2
  267. package/dist/services/authorBridgeState.cjs.map +0 -1
  268. package/dist/services/authorMode.cjs +0 -3
  269. package/dist/services/authorMode.cjs.map +0 -1
  270. package/dist/services/extensionService.cjs +0 -2
  271. package/dist/services/extensionService.cjs.map +0 -1
  272. package/dist/sessionApi.cjs +0 -1
  273. package/dist/sessionApi.d.cts +0 -7
  274. package/dist/sessionApi.d.mts +0 -7
  275. package/dist/sessionApi.mjs +0 -1
  276. package/dist/types/author.cjs +0 -2
  277. package/dist/types/author.cjs.map +0 -1
  278. package/dist/types/authorApi.cjs +0 -2
  279. package/dist/types/authorApi.cjs.map +0 -1
  280. package/dist/types/webAuthor.cjs +0 -2
  281. package/dist/types/webAuthor.cjs.map +0 -1
  282. package/dist/webHub.cjs +0 -1
  283. package/dist/webHub.d.cts +0 -2
  284. package/dist/webHub.d.mts +0 -2
  285. package/dist/webHub.mjs +0 -1
  286. package/src/exports/webClient.ts +0 -76
  287. package/src/web/api/authorBroker.ts +0 -14
  288. package/src/web/api/authorBrowserBridge.ts +0 -225
  289. package/src/web/api/authorFiles.ts +0 -219
  290. package/src/web/api/authorMedia.ts +0 -19
  291. package/src/web/api/authorRuntime.ts +0 -164
  292. package/src/web/components/AuthorDocumentPanel.tsx +0 -232
  293. package/src/web/components/AuthorGridOverlay.tsx +0 -81
  294. package/src/web/components/AuthorMediaPreview.tsx +0 -41
  295. package/src/web/components/AuthorMediaView.tsx +0 -356
  296. package/src/web/components/AuthorPanel.tsx +0 -105
  297. package/src/web/components/AuthorRegionDrafts.tsx +0 -102
  298. package/src/web/components/AuthorRequestLog.tsx +0 -179
  299. package/src/web/components/AuthorStructuredView.tsx +0 -121
  300. package/src/web/components/AuthorTextView.tsx +0 -97
  301. package/src/web/components/AuthorToolPalette.tsx +0 -103
  302. package/src/web/components/AuthorVideoControls.tsx +0 -111
  303. package/src/web/components/DescribeAuthorToolsToolCard.tsx +0 -50
  304. package/src/web/components/OpenAuthoringFileToolCard.tsx +0 -66
  305. package/src/web/components/UseAuthorToolsToolCard.tsx +0 -50
  306. package/src/web/index.ts +0 -68
  307. package/src/web/lib/authorGrid.ts +0 -124
  308. package/src/web/lib/authorRegions.ts +0 -30
  309. package/src/web/lib/authorViewportTypes.ts +0 -202
  310. package/src/web/lib/describeAuthorToolsToolRender.ts +0 -20
  311. package/src/web/lib/useAuthorToolsToolRender.ts +0 -20
  312. package/src/web/stores/authorCapture.ts +0 -322
  313. package/src/web/stores/authorGridTools.ts +0 -316
  314. package/src/web/stores/authorProfiles.ts +0 -130
  315. package/src/web/stores/authorRequestLifecycle.ts +0 -82
  316. package/src/web/stores/authorStore.ts +0 -60
  317. package/src/web/stores/authorWorkspaceStore.ts +0 -525
  318. package/src/web/tsconfig.json +0 -22
@@ -0,0 +1,4231 @@
1
+ import { apiResponse, createApiClient, defineApiRoutes, defineChannelFile, defineDockFace, defineFileLinks, defineGlobalStore, defineLeaderBinding, defineMinorModeFile, defineSessionStore, defineSlotFile, defineToolRenderer, defineWebLifecycle, defineWebPlugin, sessionApiAddress, toolResultTextLines } from "@agimon-ai/doompi-core/web";
2
+ import { Store } from "@tanstack/store";
3
+ import { Button, CodeEditor, Markdown, MediaPreview, MessageItem, MessageItemBody, MessageItemHeader, MessageItemStatus, MessageLines, collapseLines, toolTone } from "@agimon-ai/doompi-web-components";
4
+ import { useStore } from "@tanstack/react-store";
5
+ import { useEffect, useRef, useState } from "react";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { sealedTransport } from "@agimon-ai/doompi-web-security/browser";
8
+ //#region src/types/webAuthor.ts
9
+ const authorChannelType = "author_webmcp";
10
+ //#endregion
11
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/authorRuntime.ts
12
+ const AUTHOR_RUNTIME_BINDING_IDS = {
13
+ text: "author.viewport.text",
14
+ media: "author.viewport.media"
15
+ };
16
+ function assertProfile(profile) {
17
+ if (profile.id.trim() === "") throw new Error("Author profile id must not be empty");
18
+ if (profile.tools.length > 15) throw new Error("Author profiles may expose at most 15 tools");
19
+ const names = /* @__PURE__ */ new Set();
20
+ for (const tool of profile.tools) {
21
+ if (!/^[a-z][a-z0-9_]{0,29}$/.test(tool.name)) throw new Error(`Invalid Author tool name: ${tool.name}`);
22
+ if (names.has(tool.name)) throw new Error(`Duplicate Author tool name: ${tool.name}`);
23
+ names.add(tool.name);
24
+ }
25
+ }
26
+ function descriptorMatches(actual, expected) {
27
+ return actual.name === expected.name && actual.description === expected.description && JSON.stringify(actual.inputSchema) === JSON.stringify(expected.inputSchema);
28
+ }
29
+ function staleError() {
30
+ return /* @__PURE__ */ new Error("Author viewport catalog is stale");
31
+ }
32
+ function invocationSignal(catalogSignal, requestSignal) {
33
+ const controller = new AbortController();
34
+ const abortCatalog = () => controller.abort(catalogSignal.reason);
35
+ const abortRequest = () => controller.abort(requestSignal.reason);
36
+ if (catalogSignal.aborted) abortCatalog();
37
+ else if (requestSignal.aborted) abortRequest();
38
+ else {
39
+ catalogSignal.addEventListener("abort", abortCatalog, { once: true });
40
+ requestSignal.addEventListener("abort", abortRequest, { once: true });
41
+ }
42
+ return {
43
+ signal: controller.signal,
44
+ release: () => {
45
+ catalogSignal.removeEventListener("abort", abortCatalog);
46
+ requestSignal.removeEventListener("abort", abortRequest);
47
+ }
48
+ };
49
+ }
50
+ /** Owns page-lifetime WebMCP registrations. Profiles are replaced as one abortable catalog. */
51
+ var AuthorRuntime = class {
52
+ #runtime;
53
+ #context;
54
+ #active;
55
+ #pending;
56
+ #generation = 0;
57
+ #disposed = false;
58
+ constructor(runtime) {
59
+ this.#runtime = runtime;
60
+ }
61
+ async replaceProfiles(profiles) {
62
+ if (this.#disposed) throw new Error("Author runtime is disposed");
63
+ for (const profile of profiles) assertProfile(profile);
64
+ const profileMap = new Map(profiles.map((profile) => [profile.id, profile]));
65
+ const tools = profiles.flatMap((profile) => profile.tools);
66
+ if (new Set(tools.map((tool) => tool.name)).size !== tools.length) throw new Error("Author tool names must be unique across trusted profiles");
67
+ const context = this.#context ?? (await this.#runtime.acquireModelContext?.())?.modelContext;
68
+ if (context === void 0) throw new Error("Model Context is unavailable");
69
+ this.#context = context;
70
+ const generation = ++this.#generation;
71
+ const controller = new AbortController();
72
+ const candidate = {
73
+ generation,
74
+ controller,
75
+ profiles: profileMap
76
+ };
77
+ const inactive = staleError();
78
+ this.#pending?.controller.abort(inactive);
79
+ this.#active?.controller.abort(inactive);
80
+ this.#pending = candidate;
81
+ this.#active = void 0;
82
+ const assertCurrent = () => {
83
+ if (controller.signal.aborted || this.#disposed || this.#active?.generation !== generation) throw staleError();
84
+ };
85
+ try {
86
+ await Promise.all(tools.map(async (tool) => context.registerTool({
87
+ name: tool.name,
88
+ description: tool.description,
89
+ inputSchema: tool.inputSchema,
90
+ execute: async (input, options) => {
91
+ assertCurrent();
92
+ const parsed = JSON.parse(input);
93
+ assertCurrent();
94
+ const invocation = invocationSignal(controller.signal, options.signal);
95
+ try {
96
+ const result = await tool.execute(parsed, invocation.signal);
97
+ assertCurrent();
98
+ return result;
99
+ } finally {
100
+ invocation.release();
101
+ }
102
+ }
103
+ }, { signal: controller.signal })));
104
+ const registered = await context.getTools();
105
+ if (!tools.every((tool) => registered.some((descriptor) => descriptorMatches(descriptor, tool)))) throw new Error("Model Context did not confirm the Author catalog");
106
+ if (this.#disposed || generation !== this.#generation || this.#pending !== candidate) throw staleError();
107
+ this.#pending = void 0;
108
+ this.#active = candidate;
109
+ } catch (error) {
110
+ controller.abort(error);
111
+ if (this.#pending === candidate) this.#pending = void 0;
112
+ throw error;
113
+ }
114
+ return () => {
115
+ if (this.#active?.generation !== generation) return;
116
+ controller.abort(staleError());
117
+ this.#active = void 0;
118
+ this.#generation += 1;
119
+ };
120
+ }
121
+ async execute(name, input, signal) {
122
+ const context = this.#context;
123
+ if (context === void 0 || this.#active === void 0) throw new Error("Author catalog is not registered");
124
+ return context.executeTool(name, JSON.stringify(input), { signal });
125
+ }
126
+ dispose() {
127
+ if (this.#disposed) return;
128
+ this.#disposed = true;
129
+ this.#generation += 1;
130
+ this.#pending?.controller.abort(/* @__PURE__ */ new Error("Author runtime disposed"));
131
+ this.#active?.controller.abort(/* @__PURE__ */ new Error("Author runtime disposed"));
132
+ this.#pending = void 0;
133
+ this.#active = void 0;
134
+ }
135
+ };
136
+ //#endregion
137
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/authorBrowserBridge.ts
138
+ var AuthorBrowserBridge = class {
139
+ #host;
140
+ #runtime;
141
+ #pending = /* @__PURE__ */ new Map();
142
+ #active;
143
+ #generation = 0;
144
+ #disposed = false;
145
+ #releaseConnected;
146
+ #leaseTimer;
147
+ constructor(host) {
148
+ this.#host = host;
149
+ this.#runtime = new AuthorRuntime(host);
150
+ this.#releaseConnected = host.onHubConnected(() => {
151
+ if (this.#active !== void 0) {
152
+ this.#active.ownerToken = void 0;
153
+ this.#active.catalogToken = void 0;
154
+ }
155
+ this.#register();
156
+ });
157
+ }
158
+ async focus(sessionId, profiles) {
159
+ if (this.#disposed) throw new Error("Author browser bridge is disposed");
160
+ const generation = ++this.#generation;
161
+ const release = await this.#runtime.replaceProfiles(profiles);
162
+ if (this.#disposed || generation !== this.#generation) {
163
+ release();
164
+ return () => void 0;
165
+ }
166
+ this.#clearActive();
167
+ this.#active = {
168
+ sessionId,
169
+ generation,
170
+ profiles,
171
+ release
172
+ };
173
+ this.#register();
174
+ return () => {
175
+ if (this.#active?.generation !== generation) return;
176
+ this.#clearActive();
177
+ this.#generation += 1;
178
+ };
179
+ }
180
+ drop(sessionId) {
181
+ if (this.#active?.sessionId !== sessionId) return;
182
+ this.#clearActive();
183
+ this.#generation += 1;
184
+ }
185
+ apply(sessionId, message) {
186
+ const active = this.#active;
187
+ if (active === void 0 || active.sessionId !== sessionId || message.kind === "rejected") return;
188
+ if (message.generation !== active.generation || message.ownerToken !== active.ownerToken && active.ownerToken !== void 0) return;
189
+ if (message.kind === "accepted") {
190
+ active.ownerToken = message.ownerToken;
191
+ clearTimeout(this.#leaseTimer);
192
+ this.#leaseTimer = setTimeout(() => this.#register(), Math.max(1e3, Math.floor(message.leaseMs / 2)));
193
+ if (message.catalogToken === void 0) this.#sendCatalog(active);
194
+ else active.catalogToken = message.catalogToken;
195
+ return;
196
+ }
197
+ if (message.catalogToken !== active.catalogToken) return;
198
+ if (message.kind === "cancel") {
199
+ this.#pending.get(message.requestId)?.abort(/* @__PURE__ */ new Error("Author tool request cancelled"));
200
+ return;
201
+ }
202
+ if (message.kind !== "request" || this.#pending.has(message.requestId)) return;
203
+ const controller = new AbortController();
204
+ this.#pending.set(message.requestId, controller);
205
+ this.#runtime.execute(message.name, message.arguments, controller.signal).then((result) => {
206
+ if (!this.#current(active, message.requestId, controller)) return;
207
+ if (controller.signal.aborted) this.#sendCancelled(active, message.requestId);
208
+ else this.#send(active.sessionId, {
209
+ kind: "result",
210
+ generation: active.generation,
211
+ ownerToken: active.ownerToken,
212
+ catalogToken: active.catalogToken,
213
+ requestId: message.requestId,
214
+ result
215
+ });
216
+ }).catch((error) => {
217
+ if (!this.#current(active, message.requestId, controller)) return;
218
+ if (controller.signal.aborted) this.#sendCancelled(active, message.requestId);
219
+ else {
220
+ const messageText = error instanceof Error ? error.message : String(error);
221
+ const code = /^([A-Z][A-Z_]+):/u.exec(messageText)?.[1] ?? "AUTHOR_TOOL_ERROR";
222
+ this.#send(active.sessionId, {
223
+ kind: "result",
224
+ generation: active.generation,
225
+ ownerToken: active.ownerToken,
226
+ catalogToken: active.catalogToken,
227
+ requestId: message.requestId,
228
+ result: { error: {
229
+ code,
230
+ message: messageText
231
+ } }
232
+ });
233
+ }
234
+ }).finally(() => {
235
+ if (this.#pending.get(message.requestId) === controller) this.#pending.delete(message.requestId);
236
+ });
237
+ }
238
+ activeView(sessionId) {
239
+ const active = this.#active;
240
+ return active?.sessionId === sessionId && active.catalogToken !== void 0 ? {
241
+ activation: "active",
242
+ capabilityCount: active.profiles.reduce((count, profile) => count + profile.tools.length, 0)
243
+ } : {
244
+ activation: "inactive",
245
+ capabilityCount: 0
246
+ };
247
+ }
248
+ dispose() {
249
+ if (this.#disposed) return;
250
+ this.#disposed = true;
251
+ clearTimeout(this.#leaseTimer);
252
+ this.#releaseConnected();
253
+ this.#clearActive();
254
+ this.#runtime.dispose();
255
+ }
256
+ #register() {
257
+ const active = this.#active;
258
+ if (active !== void 0) this.#send(active.sessionId, {
259
+ kind: "register",
260
+ generation: active.generation
261
+ });
262
+ }
263
+ #sendCatalog(active) {
264
+ this.#send(active.sessionId, {
265
+ kind: "catalog",
266
+ generation: active.generation,
267
+ ownerToken: active.ownerToken,
268
+ tools: active.profiles.flatMap((profile) => profile.tools.map((tool) => ({
269
+ name: tool.name,
270
+ label: tool.label ?? tool.name,
271
+ description: tool.description,
272
+ inputSchema: tool.inputSchema
273
+ })))
274
+ });
275
+ }
276
+ #sendCancelled(active, requestId) {
277
+ this.#send(active.sessionId, {
278
+ kind: "cancelled",
279
+ generation: active.generation,
280
+ ownerToken: active.ownerToken,
281
+ catalogToken: active.catalogToken,
282
+ requestId
283
+ });
284
+ }
285
+ #send(sessionId, payload) {
286
+ const workspaceId = this.#host.mount?.scope === "session" ? this.#host.mount.workspaceId : void 0;
287
+ const mount = workspaceId ? {
288
+ scope: "workspace",
289
+ workspaceId
290
+ } : { scope: "global" };
291
+ this.#host.invokeServerMethod({
292
+ mount,
293
+ service: "author.bridge",
294
+ method: "send",
295
+ input: {
296
+ sessionId,
297
+ message: payload
298
+ }
299
+ }).catch(() => void 0);
300
+ }
301
+ #current(active, requestId, controller) {
302
+ return this.#active === active && this.#pending.get(requestId) === controller;
303
+ }
304
+ #clearActive() {
305
+ clearTimeout(this.#leaseTimer);
306
+ this.#leaseTimer = void 0;
307
+ const active = this.#active;
308
+ if (active !== void 0) {
309
+ this.#send(active.sessionId, {
310
+ kind: "release",
311
+ generation: active.generation
312
+ });
313
+ active.release();
314
+ }
315
+ this.#active = void 0;
316
+ for (const controller of this.#pending.values()) controller.abort(/* @__PURE__ */ new Error("Author viewport changed"));
317
+ this.#pending.clear();
318
+ }
319
+ };
320
+ const authorBridgeRuntime = new Store(void 0);
321
+ function startAuthorBrowserBridge(runtime) {
322
+ authorBridgeRuntime.state?.dispose();
323
+ const next = new AuthorBrowserBridge(runtime);
324
+ authorBridgeRuntime.setState(() => next);
325
+ return () => {
326
+ if (authorBridgeRuntime.state !== next) return;
327
+ next.dispose();
328
+ authorBridgeRuntime.setState(() => void 0);
329
+ };
330
+ }
331
+ async function focusAuthorViewport(sessionId, profiles) {
332
+ return await authorBridgeRuntime.state?.focus(sessionId, profiles) ?? (() => void 0);
333
+ }
334
+ function applyAuthorHubMessage(sessionId, message) {
335
+ authorBridgeRuntime.state?.apply(sessionId, message);
336
+ }
337
+ function dropAuthorViewportSession(sessionId) {
338
+ authorBridgeRuntime.state?.drop(sessionId);
339
+ }
340
+ function authorBridgeView(sessionId) {
341
+ return authorBridgeRuntime.state?.activeView(sessionId) ?? {
342
+ activation: "inactive",
343
+ capabilityCount: 0
344
+ };
345
+ }
346
+ //#endregion
347
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/authorGrid.ts
348
+ const AUTHOR_GRID_COLUMNS = "ABCDEFGH";
349
+ const nativeResolvers = /* @__PURE__ */ new Map();
350
+ const authorGrid = defineGlobalStore({ sessions: {} });
351
+ function parseAuthorGridCell(cell) {
352
+ const normalized = cell.trim().toUpperCase();
353
+ if (!/^[A-H][1-8]$/u.test(normalized)) throw new Error(`Invalid Author grid cell '${cell}'. Expected A1 through H8.`);
354
+ return {
355
+ cell: normalized,
356
+ column: AUTHOR_GRID_COLUMNS.indexOf(normalized[0]),
357
+ row: Number(normalized[1]) - 1
358
+ };
359
+ }
360
+ function updateAuthorGridGeometry(sessionId, input) {
361
+ const previous = authorGrid.store.state.sessions[sessionId];
362
+ const geometry = {
363
+ ...input,
364
+ geometryToken: crypto.randomUUID(),
365
+ snapshotId: previous?.documentPath === input.documentPath && previous.revision === input.revision && previous.sourceSha256 === input.sourceSha256 ? previous.snapshotId : crypto.randomUUID(),
366
+ updatedAt: Date.now()
367
+ };
368
+ authorGrid.update((state) => ({
369
+ ...state,
370
+ sessions: {
371
+ ...state.sessions,
372
+ [sessionId]: geometry
373
+ }
374
+ }));
375
+ return geometry;
376
+ }
377
+ function authorGridGeometry(sessionId) {
378
+ return authorGrid.store.state.sessions[sessionId];
379
+ }
380
+ function clearAuthorGridGeometry(sessionId) {
381
+ authorGrid.update((state) => {
382
+ if (state.sessions[sessionId] === void 0) return state;
383
+ const sessions = { ...state.sessions };
384
+ delete sessions[sessionId];
385
+ return {
386
+ ...state,
387
+ sessions
388
+ };
389
+ });
390
+ }
391
+ function registerAuthorGridResolver(sessionId, resolver) {
392
+ nativeResolvers.set(sessionId, resolver);
393
+ return () => {
394
+ if (nativeResolvers.get(sessionId) === resolver) nativeResolvers.delete(sessionId);
395
+ };
396
+ }
397
+ function resolveAuthorGridNativeAnchor(sessionId, cell) {
398
+ const geometry = authorGridGeometry(sessionId);
399
+ const resolved = geometry === void 0 ? void 0 : nativeResolvers.get(sessionId)?.(cell, geometry);
400
+ if (resolved === void 0) throw new Error("AMBIGUOUS_GRID: The grid cell does not resolve to one visible native document target.");
401
+ return resolved;
402
+ }
403
+ function resolveAuthorGridCell(sessionId, cell, expectedGeometryToken) {
404
+ const geometry = authorGridGeometry(sessionId);
405
+ if (geometry === void 0 || geometry.geometryToken !== expectedGeometryToken) throw new Error("STALE_GRID: The Author grid geometry changed. Describe the grid again.");
406
+ const parsed = parseAuthorGridCell(cell);
407
+ const width = 1 / 8;
408
+ const height = 1 / 8;
409
+ const rect = {
410
+ x: parsed.column * width,
411
+ y: parsed.row * height,
412
+ width,
413
+ height
414
+ };
415
+ return {
416
+ ...parsed,
417
+ rect,
418
+ center: {
419
+ x: rect.x + width / 2,
420
+ y: rect.y + height / 2
421
+ },
422
+ evidence: {
423
+ cell: parsed.cell,
424
+ geometryToken: geometry.geometryToken,
425
+ snapshotId: geometry.snapshotId
426
+ }
427
+ };
428
+ }
429
+ const TERMINAL_REQUEST_STATUSES = /* @__PURE__ */ new Set([
430
+ "COMPLETE",
431
+ "FAILED",
432
+ "CANCELLED"
433
+ ]);
434
+ const EMPTY_SESSION = {
435
+ generation: 0,
436
+ activeTool: "select",
437
+ annotationsByDocument: {},
438
+ annotations: [],
439
+ candidateText: "",
440
+ regions: [],
441
+ requests: []
442
+ };
443
+ function normalizeAuthorPath(path) {
444
+ const parts = [];
445
+ for (const part of path.trim().replaceAll("\\", "/").split("/")) {
446
+ if (part === "" || part === ".") continue;
447
+ if (part === "..") parts.pop();
448
+ else parts.push(part);
449
+ }
450
+ return parts.join("/");
451
+ }
452
+ function authorDocumentKey(sessionId, path) {
453
+ return `${sessionId}\n${normalizeAuthorPath(path)}`;
454
+ }
455
+ const authorWorkspace = defineGlobalStore({
456
+ documents: {},
457
+ sessions: {}
458
+ });
459
+ function authorDocument(sessionId, path) {
460
+ if (sessionId === null) return void 0;
461
+ return authorWorkspace.store.state.documents[authorDocumentKey(sessionId, path)];
462
+ }
463
+ function authorSessionWorkspace(sessionId) {
464
+ if (sessionId === null) return EMPTY_SESSION;
465
+ return authorWorkspace.store.state.sessions[sessionId] ?? EMPTY_SESSION;
466
+ }
467
+ function hydrateAuthorAnnotationCollections(records) {
468
+ authorWorkspace.update((state) => {
469
+ let sessions = state.sessions;
470
+ for (const record of records) {
471
+ const path = normalizeAuthorPath(record.documentPath);
472
+ if (path === "") continue;
473
+ const session = sessions[record.sessionId] ?? EMPTY_SESSION;
474
+ const previous = session.annotationsByDocument[path];
475
+ if (previous !== void 0 && previous.updatedAt >= record.collection.updatedAt) continue;
476
+ const document = state.documents[authorDocumentKey(record.sessionId, path)];
477
+ const stale = record.collection.stale || document !== void 0 && (document.version !== record.collection.revision || document.sourceSha256 !== record.collection.sourceSha256);
478
+ const collection = restoreCollectionEvidence({
479
+ ...record.collection,
480
+ stale
481
+ });
482
+ const visible = session.focusedDocument?.path === path && !stale;
483
+ const annotations = visible ? collection.annotations : session.annotations;
484
+ sessions = {
485
+ ...sessions,
486
+ [record.sessionId]: {
487
+ ...session,
488
+ annotationsByDocument: {
489
+ ...session.annotationsByDocument,
490
+ [path]: collection
491
+ },
492
+ ...visible ? {
493
+ annotations,
494
+ regions: annotations,
495
+ candidate: collection.candidate,
496
+ candidateText: collection.candidateText
497
+ } : {}
498
+ }
499
+ };
500
+ }
501
+ return sessions === state.sessions ? state : {
502
+ ...state,
503
+ sessions
504
+ };
505
+ });
506
+ }
507
+ function putAuthorDocument(sessionId, input) {
508
+ const path = normalizeAuthorPath(input.path);
509
+ if (path === "") throw new Error("Author document path must not be empty");
510
+ const key = authorDocumentKey(sessionId, path);
511
+ const staleThumbnails = [];
512
+ let result;
513
+ authorWorkspace.update((state) => {
514
+ const previous = state.documents[key];
515
+ const sourceChanged = previous?.sourceSha256 !== void 0 && input.sourceSha256 !== void 0 && previous.sourceSha256 !== input.sourceSha256;
516
+ const baselineVersion = sourceChanged ? previous.version + 1 : previous?.version ?? 0;
517
+ result = {
518
+ ...input,
519
+ path,
520
+ annotations: sourceChanged ? [] : previous?.annotations ?? [],
521
+ revisions: sourceChanged ? [] : previous?.revisions ?? [],
522
+ saveRequest: previous?.saveRequest ?? 0,
523
+ version: baselineVersion,
524
+ savedVersion: sourceChanged ? baselineVersion : previous?.savedVersion ?? 0,
525
+ ...sourceChanged || previous?.crop === void 0 ? {} : { crop: previous.crop }
526
+ };
527
+ const session = state.sessions[sessionId];
528
+ let sessions = state.sessions;
529
+ if (sourceChanged && session?.focusedDocument?.path === path) {
530
+ staleThumbnails.push(...session.candidate?.thumbnailUrl === void 0 ? [] : [session.candidate.thumbnailUrl], ...session.annotations.flatMap((annotation) => annotation.thumbnailUrl === void 0 ? [] : [annotation.thumbnailUrl]));
531
+ const now = Date.now();
532
+ const generation = session.generation + 1;
533
+ const staleCollection = staleAuthorCollection(session.annotationsByDocument[path], now);
534
+ sessions = {
535
+ ...sessions,
536
+ [sessionId]: {
537
+ ...session,
538
+ generation,
539
+ focusedDocument: {
540
+ path,
541
+ generation,
542
+ revision: baselineVersion,
543
+ sourceSha256: input.sourceSha256,
544
+ focusedAt: now
545
+ },
546
+ annotationsByDocument: {
547
+ ...session.annotationsByDocument,
548
+ ...staleCollection && { [path]: staleCollection }
549
+ },
550
+ annotations: [],
551
+ regions: [],
552
+ candidate: void 0,
553
+ candidateText: "",
554
+ requests: session.requests.map((request) => input.kind !== "story-preview" && request.documentPath === path && (request.status === "REQUESTED" || request.status === "CHANGING" || request.status === "CHANGED") ? {
555
+ ...request,
556
+ status: "FAILED",
557
+ currentOperation: void 0,
558
+ error: "The document source changed before the request completed.",
559
+ updatedAt: now
560
+ } : request)
561
+ }
562
+ };
563
+ } else if (sourceChanged && session?.annotationsByDocument[path] !== void 0) {
564
+ const collection = session.annotationsByDocument[path];
565
+ if (collection.candidate?.thumbnailUrl !== void 0) staleThumbnails.push(collection.candidate.thumbnailUrl);
566
+ staleThumbnails.push(...collection.annotations.flatMap((annotation) => annotation.thumbnailUrl === void 0 ? [] : [annotation.thumbnailUrl]));
567
+ sessions = {
568
+ ...sessions,
569
+ [sessionId]: {
570
+ ...session,
571
+ annotationsByDocument: {
572
+ ...session.annotationsByDocument,
573
+ [path]: staleAuthorCollection(collection, Date.now())
574
+ }
575
+ }
576
+ };
577
+ }
578
+ return {
579
+ documents: {
580
+ ...state.documents,
581
+ [key]: result
582
+ },
583
+ sessions
584
+ };
585
+ });
586
+ staleThumbnails.forEach(revokeThumbnail);
587
+ return result;
588
+ }
589
+ /** Claims focus and returns a generation token that makes cleanup race-safe. */
590
+ function focusAuthorDocument(sessionId, path, revision, sourceSha256) {
591
+ const normalized = normalizeAuthorPath(path);
592
+ let generation = 0;
593
+ authorWorkspace.update((state) => {
594
+ const session = state.sessions[sessionId] ?? EMPTY_SESSION;
595
+ generation = session.generation + 1;
596
+ const stored = session.annotationsByDocument[normalized];
597
+ const current = stored !== void 0 && stored.revision === revision && stored.sourceSha256 === sourceSha256 && !stored.stale ? stored : void 0;
598
+ const annotations = current?.annotations ?? [];
599
+ return {
600
+ ...state,
601
+ sessions: {
602
+ ...state.sessions,
603
+ [sessionId]: {
604
+ ...session,
605
+ generation,
606
+ focusedDocument: {
607
+ path: normalized,
608
+ generation,
609
+ revision,
610
+ sourceSha256,
611
+ focusedAt: Date.now()
612
+ },
613
+ annotations,
614
+ regions: annotations,
615
+ candidate: current?.candidate,
616
+ candidateText: current?.candidateText ?? ""
617
+ }
618
+ }
619
+ };
620
+ });
621
+ return generation;
622
+ }
623
+ function syncAuthorDocumentFocus(sessionId, generation, revision, sourceSha256) {
624
+ updateSession(sessionId, (session) => {
625
+ const focused = session.focusedDocument;
626
+ if (focused === void 0 || focused.generation !== generation) return session;
627
+ if (focused.revision === revision && focused.sourceSha256 === sourceSha256) return session;
628
+ return {
629
+ ...session,
630
+ focusedDocument: {
631
+ ...focused,
632
+ revision,
633
+ sourceSha256
634
+ }
635
+ };
636
+ });
637
+ }
638
+ function releaseAuthorDocumentFocus(sessionId, generation) {
639
+ updateSession(sessionId, (session) => {
640
+ if (session.focusedDocument?.generation !== generation) return session;
641
+ return {
642
+ ...session,
643
+ focusedDocument: void 0,
644
+ candidate: void 0,
645
+ candidateText: "",
646
+ annotations: [],
647
+ regions: []
648
+ };
649
+ });
650
+ }
651
+ function seekAuthorVideo(sessionId, timeSeconds) {
652
+ const workspace = authorSessionWorkspace(sessionId);
653
+ const focused = workspace.focusedDocument;
654
+ if (!focused || workspace.candidate || !Number.isFinite(timeSeconds) || timeSeconds < 0 || authorDocument(sessionId, focused.path)?.kind !== "video") return false;
655
+ updateSession(sessionId, (session) => ({
656
+ ...session,
657
+ activeTool: "select",
658
+ videoSeekRequest: {
659
+ path: focused.path,
660
+ generation: focused.generation,
661
+ timeSeconds,
662
+ sequence: (session.videoSeekRequest?.sequence ?? 0) + 1
663
+ }
664
+ }));
665
+ return true;
666
+ }
667
+ function setAuthorToolMode(sessionId, activeTool) {
668
+ updateSession(sessionId, (session) => session.activeTool === activeTool ? session : {
669
+ ...session,
670
+ activeTool
671
+ });
672
+ }
673
+ function setAuthorRegionCandidate(sessionId, candidate) {
674
+ let staleThumbnail;
675
+ updateSession(sessionId, (session) => {
676
+ const focused = session.focusedDocument;
677
+ if (candidate !== void 0) {
678
+ if (focused === void 0 || focused.path !== normalizeAuthorPath(candidate.documentPath)) throw new Error("The Author selection does not belong to the focused document.");
679
+ if (focused.revision !== candidate.revision || focused.sourceSha256 !== candidate.sourceSha256) throw new Error("The Author selection is stale for the focused document.");
680
+ }
681
+ if (focused === void 0) return session;
682
+ staleThumbnail = session.candidate?.thumbnailUrl;
683
+ const copied = candidate === void 0 ? void 0 : copyCandidate(candidate);
684
+ return withFocusedCollection({
685
+ ...session,
686
+ candidate: copied
687
+ }, focused.path, { candidate: copied });
688
+ });
689
+ if (staleThumbnail !== void 0 && staleThumbnail !== candidate?.thumbnailUrl) revokeThumbnail(staleThumbnail);
690
+ }
691
+ function setAuthorCandidateText(sessionId, candidateText) {
692
+ if (new TextEncoder().encode(candidateText).byteLength > 2048) throw new Error("Author candidate text must not exceed 2 KiB.");
693
+ updateSession(sessionId, (session) => {
694
+ const path = session.focusedDocument?.path;
695
+ return path === void 0 ? session : withFocusedCollection({
696
+ ...session,
697
+ candidateText
698
+ }, path, { candidateText });
699
+ });
700
+ }
701
+ function commitAuthorRegion(sessionId, comment) {
702
+ const id = crypto.randomUUID();
703
+ const session = authorSessionWorkspace(sessionId);
704
+ if (session.candidate === void 0) throw new Error("Select a document annotation before adding a comment.");
705
+ addAuthorRegion(sessionId, {
706
+ ...session.candidate,
707
+ id,
708
+ comment,
709
+ version: 1
710
+ });
711
+ updateSession(sessionId, (current) => {
712
+ const path = current.focusedDocument?.path;
713
+ return path === void 0 ? current : withFocusedCollection({
714
+ ...current,
715
+ candidate: void 0,
716
+ candidateText: ""
717
+ }, path, {
718
+ candidate: void 0,
719
+ candidateText: ""
720
+ });
721
+ });
722
+ return id;
723
+ }
724
+ function addAuthorRegion(sessionId, region) {
725
+ if (region.comment.trim() === "") throw new Error("Every Author annotation requires a comment.");
726
+ updateSession(sessionId, (session) => {
727
+ const focused = session.focusedDocument;
728
+ if (focused === void 0 || focused.path !== normalizeAuthorPath(region.documentPath)) throw new Error("The Author annotation does not belong to the focused document.");
729
+ if (focused.revision !== region.revision || focused.sourceSha256 !== region.sourceSha256) throw new Error("The Author annotation is stale for the focused document.");
730
+ if (session.annotations.length >= 16) throw new Error(`Author requests support at most 16 annotations.`);
731
+ if (session.annotations.some((candidate) => candidate.id === region.id)) throw new Error(`Author annotation '${region.id}' already exists.`);
732
+ const annotations = [...session.annotations, copyRegion({
733
+ ...region,
734
+ version: region.version ?? 1
735
+ })];
736
+ return withFocusedCollection({
737
+ ...session,
738
+ annotations,
739
+ regions: annotations
740
+ }, focused.path, { annotations });
741
+ });
742
+ }
743
+ function removeAuthorRegion(sessionId, regionId, expectedVersion, documentPath) {
744
+ let thumbnail;
745
+ updateSession(sessionId, (session) => {
746
+ const path = documentPath === void 0 ? session.focusedDocument?.path : normalizeAuthorPath(documentPath);
747
+ if (path === void 0) return session;
748
+ const focused = session.focusedDocument?.path === path;
749
+ const collection = session.annotationsByDocument[path];
750
+ const current = focused ? session.annotations : collection?.annotations;
751
+ if (current === void 0) return session;
752
+ const annotation = current.find((candidate) => candidate.id === regionId);
753
+ if (annotation === void 0 || expectedVersion !== void 0 && (annotation.version ?? 1) !== expectedVersion) return session;
754
+ thumbnail = annotation.thumbnailUrl;
755
+ const annotations = current.filter((candidate) => candidate.id !== regionId);
756
+ if (focused) return withFocusedCollection({
757
+ ...session,
758
+ annotations,
759
+ regions: annotations
760
+ }, path, { annotations });
761
+ return {
762
+ ...session,
763
+ annotationsByDocument: {
764
+ ...session.annotationsByDocument,
765
+ [path]: {
766
+ ...collection,
767
+ annotations,
768
+ updatedAt: Date.now()
769
+ }
770
+ }
771
+ };
772
+ });
773
+ if (thumbnail !== void 0) revokeThumbnail(thumbnail);
774
+ }
775
+ function putAuthorRequest(sessionId, record) {
776
+ if (record.requestText.trim() === "") throw new Error("An Author request requires verbatim request text.");
777
+ if (record.regions.length === 0 || record.regions.some((region) => region.comment.trim() === "")) throw new Error("An Author request requires at least one commented region.");
778
+ updateSession(sessionId, (session) => {
779
+ if (session.requests.some((request) => request.id === record.id)) throw new Error(`Author request '${record.id}' already exists.`);
780
+ return {
781
+ ...session,
782
+ requests: boundHistory([...session.requests, copyRequest(record)])
783
+ };
784
+ });
785
+ }
786
+ function updateAuthorRequest(sessionId, requestId, update) {
787
+ updateSession(sessionId, (session) => ({
788
+ ...session,
789
+ requests: boundHistory(session.requests.map((request) => request.id === requestId ? copyRequest(update(request)) : request))
790
+ }));
791
+ }
792
+ function reviseAuthorDocument(sessionId, path, content) {
793
+ updateDocument(sessionId, path, (document) => {
794
+ if (document.content === content) return document;
795
+ const version = document.version + 1;
796
+ return {
797
+ ...document,
798
+ content,
799
+ version,
800
+ revisions: [...document.revisions, {
801
+ revision: version,
802
+ content
803
+ }]
804
+ };
805
+ });
806
+ }
807
+ function reviseAuthorFragment(sessionId, path, fragmentId, text) {
808
+ updateDocument(sessionId, path, (document) => {
809
+ const fragment = document.fragments?.find((candidate) => candidate.id === fragmentId);
810
+ if (fragment === void 0 || fragment.text === text) return document;
811
+ const version = document.version + 1;
812
+ return {
813
+ ...document,
814
+ fragments: document.fragments?.map((fragment) => fragment.id === fragmentId ? {
815
+ ...fragment,
816
+ text
817
+ } : fragment),
818
+ version,
819
+ revisions: [...document.revisions, {
820
+ revision: version,
821
+ content: text
822
+ }]
823
+ };
824
+ });
825
+ }
826
+ function addAuthorAnnotation(sessionId, path, annotation) {
827
+ updateDocument(sessionId, path, (document) => ({
828
+ ...document,
829
+ annotations: [...document.annotations, annotation]
830
+ }));
831
+ }
832
+ function setAuthorCrop(sessionId, path, crop) {
833
+ updateDocument(sessionId, path, (document) => {
834
+ const version = document.version + 1;
835
+ return {
836
+ ...document,
837
+ ...crop === void 0 ? { crop: void 0 } : { crop },
838
+ version,
839
+ revisions: [...document.revisions, {
840
+ revision: version,
841
+ content: JSON.stringify(crop ?? null)
842
+ }]
843
+ };
844
+ });
845
+ }
846
+ function requestAuthorSave(sessionId, path) {
847
+ updateDocument(sessionId, path, (document) => ({
848
+ ...document,
849
+ saveRequest: document.saveRequest + 1,
850
+ savingVersion: document.version
851
+ }));
852
+ }
853
+ function failAuthorSave(sessionId, path, savingVersion) {
854
+ updateDocument(sessionId, path, (document) => document.savingVersion === savingVersion ? {
855
+ ...document,
856
+ savingVersion: void 0
857
+ } : document);
858
+ }
859
+ function completeAuthorSave(sessionId, path, sourceSha256, savedVersion, savedFragments) {
860
+ updateDocument(sessionId, path, (document) => {
861
+ if (savedVersion < document.savedVersion) return document;
862
+ return {
863
+ ...document,
864
+ sourceSha256,
865
+ savedVersion,
866
+ savingVersion: document.savingVersion === savedVersion ? void 0 : document.savingVersion,
867
+ originalFragments: savedFragments?.map((fragment) => ({ ...fragment })),
868
+ ...document.version === savedVersion ? { crop: void 0 } : {},
869
+ revisions: document.revisions.filter((revision) => revision.revision > savedVersion)
870
+ };
871
+ });
872
+ const now = Date.now();
873
+ updateSession(sessionId, (session) => ({
874
+ ...session,
875
+ requests: boundHistory(session.requests.map((request) => request.documentPath === normalizeAuthorPath(path) && request.status === "CHANGED" && request.revision <= savedVersion ? {
876
+ ...request,
877
+ status: "COMPLETE",
878
+ currentOperation: void 0,
879
+ updatedAt: now
880
+ } : request))
881
+ }));
882
+ }
883
+ function dropAuthorSession(sessionId) {
884
+ const prefix = `${sessionId}\n`;
885
+ const session = authorWorkspace.store.state.sessions[sessionId];
886
+ for (const collection of Object.values(session?.annotationsByDocument ?? {})) {
887
+ if (collection.candidate?.thumbnailUrl !== void 0) revokeThumbnail(collection.candidate.thumbnailUrl);
888
+ collection.annotations.forEach((annotation) => {
889
+ if (annotation.thumbnailUrl !== void 0) revokeThumbnail(annotation.thumbnailUrl);
890
+ });
891
+ }
892
+ authorWorkspace.update((state) => {
893
+ const sessions = { ...state.sessions };
894
+ delete sessions[sessionId];
895
+ return {
896
+ documents: Object.fromEntries(Object.entries(state.documents).filter(([key]) => !key.startsWith(prefix))),
897
+ sessions
898
+ };
899
+ });
900
+ }
901
+ function setAuthorStoryPreview(sessionId, path, storyPreview) {
902
+ updateDocument(sessionId, path, (document) => ({
903
+ ...document,
904
+ storyPreview: structuredClone(storyPreview)
905
+ }));
906
+ }
907
+ function updateDocument(sessionId, path, update) {
908
+ const key = authorDocumentKey(sessionId, path);
909
+ const staleThumbnails = [];
910
+ authorWorkspace.update((state) => {
911
+ const document = state.documents[key];
912
+ if (document === void 0) return state;
913
+ const next = update(document);
914
+ const session = state.sessions[sessionId];
915
+ const focused = session?.focusedDocument;
916
+ const identityChanged = focused?.path === document.path && (focused.revision !== next.version || focused.sourceSha256 !== next.sourceSha256);
917
+ if (identityChanged) staleThumbnails.push(...session.candidate?.thumbnailUrl === void 0 ? [] : [session.candidate.thumbnailUrl], ...session.annotations.flatMap((annotation) => annotation.thumbnailUrl === void 0 ? [] : [annotation.thumbnailUrl]));
918
+ const staleCollection = identityChanged ? staleAuthorCollection(session.annotationsByDocument[document.path], Date.now()) : void 0;
919
+ const sessions = identityChanged ? {
920
+ ...state.sessions,
921
+ [sessionId]: {
922
+ ...session,
923
+ focusedDocument: {
924
+ ...focused,
925
+ revision: next.version,
926
+ sourceSha256: next.sourceSha256
927
+ },
928
+ annotationsByDocument: {
929
+ ...session.annotationsByDocument,
930
+ ...staleCollection && { [document.path]: staleCollection }
931
+ },
932
+ candidate: void 0,
933
+ candidateText: "",
934
+ annotations: [],
935
+ regions: []
936
+ }
937
+ } : state.sessions;
938
+ return {
939
+ documents: {
940
+ ...state.documents,
941
+ [key]: next
942
+ },
943
+ sessions
944
+ };
945
+ });
946
+ staleThumbnails.forEach(revokeThumbnail);
947
+ }
948
+ function updateSession(sessionId, update) {
949
+ authorWorkspace.update((state) => {
950
+ const session = state.sessions[sessionId] ?? EMPTY_SESSION;
951
+ const next = update(session);
952
+ return next === session ? state : {
953
+ ...state,
954
+ sessions: {
955
+ ...state.sessions,
956
+ [sessionId]: next
957
+ }
958
+ };
959
+ });
960
+ }
961
+ function copyCandidate(candidate) {
962
+ const anchor = "rect" in candidate.anchor ? {
963
+ ...candidate.anchor,
964
+ rect: { ...candidate.anchor.rect }
965
+ } : "point" in candidate.anchor ? {
966
+ ...candidate.anchor,
967
+ point: { ...candidate.anchor.point }
968
+ } : { ...candidate.anchor };
969
+ return {
970
+ ...candidate,
971
+ anchor,
972
+ viewport: { ...candidate.viewport },
973
+ voiceGrid: candidate.voiceGrid && { ...candidate.voiceGrid }
974
+ };
975
+ }
976
+ function copyRegion(region) {
977
+ return copyCandidate(region);
978
+ }
979
+ function staleAuthorCollection(collection, updatedAt) {
980
+ if (collection === void 0) return void 0;
981
+ return {
982
+ ...collection,
983
+ stale: true,
984
+ candidate: collection.candidate && {
985
+ ...copyCandidate(collection.candidate),
986
+ thumbnailUrl: void 0
987
+ },
988
+ annotations: collection.annotations.map((annotation) => ({
989
+ ...copyRegion(annotation),
990
+ thumbnailUrl: void 0
991
+ })),
992
+ updatedAt
993
+ };
994
+ }
995
+ function withFocusedCollection(session, path, update) {
996
+ const focused = session.focusedDocument;
997
+ if (focused === void 0 || focused.path !== path) return session;
998
+ const collection = {
999
+ ...session.annotationsByDocument[path],
1000
+ revision: focused.revision,
1001
+ sourceSha256: focused.sourceSha256,
1002
+ stale: false,
1003
+ candidate: session.candidate,
1004
+ candidateText: session.candidateText,
1005
+ annotations: session.annotations,
1006
+ updatedAt: Date.now(),
1007
+ ...update
1008
+ };
1009
+ return {
1010
+ ...session,
1011
+ annotationsByDocument: {
1012
+ ...session.annotationsByDocument,
1013
+ [path]: collection
1014
+ }
1015
+ };
1016
+ }
1017
+ function boundedHistoryText(value) {
1018
+ if (value === void 0) return void 0;
1019
+ const encoder = new TextEncoder();
1020
+ if (encoder.encode(value).byteLength <= 16384) return value;
1021
+ let end = value.length;
1022
+ while (end > 0 && encoder.encode(value.slice(0, end)).byteLength > 16384) end -= 256;
1023
+ return value.slice(0, end);
1024
+ }
1025
+ function copyRequest(record) {
1026
+ return {
1027
+ ...record,
1028
+ before: boundedHistoryText(record.before),
1029
+ after: boundedHistoryText(record.after),
1030
+ regions: record.regions.map(copyRegion),
1031
+ pendingRegions: record.pendingRegions?.map(copyRegion)
1032
+ };
1033
+ }
1034
+ function boundHistory(records) {
1035
+ const next = [...records];
1036
+ const bytes = () => new TextEncoder().encode(JSON.stringify(next)).byteLength;
1037
+ while (next.length > 100 || bytes() > 524288) {
1038
+ const terminal = next.findIndex((record) => TERMINAL_REQUEST_STATUSES.has(record.status));
1039
+ if (terminal < 0) throw new Error("Author request history is full while active requests are still running.");
1040
+ next.splice(terminal, 1);
1041
+ }
1042
+ return next;
1043
+ }
1044
+ function restoreCollectionEvidence(collection) {
1045
+ const restore = (item) => {
1046
+ const copied = copyCandidate(item);
1047
+ const existingVersion = Reflect.get(copied, "version");
1048
+ const versioned = "id" in copied ? {
1049
+ ...copied,
1050
+ version: typeof existingVersion === "number" ? existingVersion : 1
1051
+ } : copied;
1052
+ if (versioned.thumbnailUrl !== void 0 || versioned.evidence === void 0) return versioned;
1053
+ try {
1054
+ return {
1055
+ ...versioned,
1056
+ thumbnailUrl: URL.createObjectURL(versioned.evidence)
1057
+ };
1058
+ } catch {
1059
+ return versioned;
1060
+ }
1061
+ };
1062
+ return {
1063
+ ...collection,
1064
+ candidate: collection.candidate && restore(collection.candidate),
1065
+ annotations: collection.annotations.map(restore)
1066
+ };
1067
+ }
1068
+ function revokeThumbnail(url) {
1069
+ if (!url.startsWith("blob:")) return;
1070
+ try {
1071
+ URL.revokeObjectURL(url);
1072
+ } catch {}
1073
+ }
1074
+ //#endregion
1075
+ //#region src/extensions/workspaces/sessions/(frontend)/channel/_lib/authorStore.ts
1076
+ const author = defineSessionStore({
1077
+ activation: "inactive",
1078
+ capabilityCount: 0
1079
+ });
1080
+ function isRecord(value) {
1081
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1082
+ }
1083
+ function parseAuthorHubMessage(input) {
1084
+ if (!isRecord(input)) return null;
1085
+ if ((input.activation === "active" || input.activation === "inactive") && Number.isSafeInteger(input.capabilityCount) && input.capabilityCount >= 0) return input;
1086
+ if (typeof input.kind !== "string") return null;
1087
+ if (input.kind === "rejected") return typeof input.reason === "string" ? input : null;
1088
+ if (!Number.isSafeInteger(input.generation) || typeof input.ownerToken !== "string") return null;
1089
+ if (input.kind === "accepted") return typeof input.leaseMs === "number" && (input.catalogToken === void 0 || typeof input.catalogToken === "string") ? input : null;
1090
+ if ((input.kind === "request" || input.kind === "cancel") && typeof input.catalogToken === "string" && typeof input.requestId === "string") {
1091
+ if (input.kind === "cancel") return input;
1092
+ return typeof input.name === "string" && isRecord(input.arguments) ? input : null;
1093
+ }
1094
+ return null;
1095
+ }
1096
+ //#endregion
1097
+ //#region src/extensions/workspaces/sessions/(frontend)/channel/author-webmcp.web.ts
1098
+ var author_webmcp_web_default = defineChannelFile({
1099
+ channel: authorChannelType,
1100
+ parse: parseAuthorHubMessage,
1101
+ apply(sessionId, message) {
1102
+ if ("activation" in message) {
1103
+ author.update(sessionId, () => message);
1104
+ return;
1105
+ }
1106
+ applyAuthorHubMessage(sessionId, message);
1107
+ author.update(sessionId, () => authorBridgeView(sessionId));
1108
+ },
1109
+ drop(sessionId) {
1110
+ clearAuthorGridGeometry(sessionId);
1111
+ dropAuthorViewportSession(sessionId);
1112
+ dropAuthorSession(sessionId);
1113
+ author.drop(sessionId);
1114
+ }
1115
+ });
1116
+ //#endregion
1117
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorGridOverlay.tsx
1118
+ function autonomousVoiceGridVisible(statuses) {
1119
+ return statuses["doom-voice"]?.trimStart().startsWith("voice auto:") === true;
1120
+ }
1121
+ function AuthorGridOverlay({ sessionId, document, visible }) {
1122
+ const host = useRef(null);
1123
+ useEffect(() => {
1124
+ const element = host.current?.parentElement;
1125
+ if (!visible || element === null || element === void 0) {
1126
+ clearAuthorGridGeometry(sessionId);
1127
+ return;
1128
+ }
1129
+ const publish = () => {
1130
+ const bounds = element.getBoundingClientRect();
1131
+ updateAuthorGridGeometry(sessionId, {
1132
+ documentPath: document.path,
1133
+ revision: document.version,
1134
+ sourceSha256: document.sourceSha256,
1135
+ viewport: {
1136
+ width: bounds.width,
1137
+ height: bounds.height,
1138
+ originX: bounds.left,
1139
+ originY: bounds.top,
1140
+ scrollX: element.scrollLeft,
1141
+ scrollY: element.scrollTop,
1142
+ zoom: window.devicePixelRatio
1143
+ }
1144
+ });
1145
+ };
1146
+ publish();
1147
+ const observer = new ResizeObserver(publish);
1148
+ observer.observe(element);
1149
+ element.addEventListener("scroll", publish, {
1150
+ passive: true,
1151
+ capture: true
1152
+ });
1153
+ window.addEventListener("focus", publish);
1154
+ return () => {
1155
+ observer.disconnect();
1156
+ element.removeEventListener("scroll", publish, true);
1157
+ window.removeEventListener("focus", publish);
1158
+ clearAuthorGridGeometry(sessionId);
1159
+ };
1160
+ }, [
1161
+ document.path,
1162
+ document.sourceSha256,
1163
+ document.version,
1164
+ sessionId,
1165
+ visible
1166
+ ]);
1167
+ if (!visible) return null;
1168
+ return /* @__PURE__ */ jsx("div", {
1169
+ ref: host,
1170
+ "data-testid": "author-grid",
1171
+ "aria-hidden": "true",
1172
+ className: "pointer-events-none absolute inset-0 z-20 grid grid-cols-8 grid-rows-8",
1173
+ children: Array.from({ length: 64 }, (_, index) => {
1174
+ const column = index % 8;
1175
+ const row = Math.floor(index / 8);
1176
+ return /* @__PURE__ */ jsx("div", {
1177
+ className: "relative border border-doom-red/25",
1178
+ children: /* @__PURE__ */ jsxs("span", {
1179
+ className: "absolute left-1 top-0.5 rounded bg-doom-deep/80 px-1 text-2xs font-bold text-doom-red",
1180
+ children: [AUTHOR_GRID_COLUMNS[column], row + 1]
1181
+ })
1182
+ }, index);
1183
+ })
1184
+ });
1185
+ }
1186
+ //#endregion
1187
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/authorCapture.ts
1188
+ const AUTHOR_CAPTURE_MAX_DIMENSION = 1600;
1189
+ const AUTHOR_QUOTE_MAX_BYTES = 4096;
1190
+ const AUTHOR_REGION_COLORS = [
1191
+ "#ff5f57",
1192
+ "#00c2ff",
1193
+ "#ffd166",
1194
+ "#b388ff",
1195
+ "#4ade80",
1196
+ "#ff8c42",
1197
+ "#f472b6",
1198
+ "#2dd4bf",
1199
+ "#a3e635",
1200
+ "#60a5fa",
1201
+ "#e879f9",
1202
+ "#facc15",
1203
+ "#34d399",
1204
+ "#fb7185",
1205
+ "#818cf8",
1206
+ "#22d3ee"
1207
+ ];
1208
+ function annotationForeground(color) {
1209
+ const red = Number.parseInt(color.slice(1, 3), 16);
1210
+ const green = Number.parseInt(color.slice(3, 5), 16);
1211
+ const blue = Number.parseInt(color.slice(5, 7), 16);
1212
+ return red * 299 + green * 587 + blue * 114 >= 15e4 ? "#101216" : "#ffffff";
1213
+ }
1214
+ const bytes = (value) => new TextEncoder().encode(value).byteLength;
1215
+ function utf8Prefix(value, limit) {
1216
+ if (bytes(value) <= limit) return value;
1217
+ let low = 0;
1218
+ let high = value.length;
1219
+ while (low < high) {
1220
+ const middle = Math.ceil((low + high) / 2);
1221
+ if (bytes(value.slice(0, middle)) <= limit) low = middle;
1222
+ else high = middle - 1;
1223
+ }
1224
+ return value.slice(0, low);
1225
+ }
1226
+ function createAuthorCapturePacket(captureId, capturedAt, document, regions) {
1227
+ if (regions.length === 0 || regions.length > 16) throw new Error("Capture requires between 1 and 16 regions.");
1228
+ if (document.kind === "story-preview" && document.storyPreview === void 0) throw new Error("Story preview capture requires source provenance.");
1229
+ const packet = {
1230
+ version: 1,
1231
+ captureId,
1232
+ capturedAt,
1233
+ document: {
1234
+ path: document.path,
1235
+ kind: document.kind,
1236
+ revision: document.version,
1237
+ ...document.sourceSha256 === void 0 ? {} : { sourceSha256: document.sourceSha256 },
1238
+ ...document.storyPreview === void 0 ? {} : { storyPreview: structuredClone(document.storyPreview) }
1239
+ },
1240
+ regions: regions.map((region, index) => {
1241
+ if (region.documentPath !== document.path || region.revision !== document.version || region.sourceSha256 !== document.sourceSha256) throw new Error(`Region ${String(index + 1)} is stale for the focused document.`);
1242
+ if (region.comment.trim() === "") throw new Error(`Region ${String(index + 1)} requires a comment.`);
1243
+ if (bytes(region.comment) > 2048) throw new Error(`Region ${String(index + 1)} comment exceeds 2 KiB.`);
1244
+ return {
1245
+ id: region.id,
1246
+ ordinal: index + 1,
1247
+ comment: region.comment,
1248
+ version: region.version ?? 1,
1249
+ mode: region.mode ?? ("point" in region.anchor ? "point" : "region"),
1250
+ ...region.quote === void 0 ? {} : { quote: utf8Prefix(region.quote, AUTHOR_QUOTE_MAX_BYTES) },
1251
+ anchor: structuredClone(region.anchor),
1252
+ viewport: { ...region.viewport },
1253
+ ...region.voiceGrid === void 0 ? {} : { voiceGrid: { ...region.voiceGrid } }
1254
+ };
1255
+ })
1256
+ };
1257
+ if (bytes(JSON.stringify(packet)) > 65536) throw new Error("Author capture packet exceeds 64 KiB.");
1258
+ return packet;
1259
+ }
1260
+ function percent(value) {
1261
+ return `${String(Math.round(value * 1e3) / 10)}%`;
1262
+ }
1263
+ function rectLocation(rect) {
1264
+ return `x ${percent(rect.x)}, y ${percent(rect.y)}, w ${percent(rect.width)}, h ${percent(rect.height)}`;
1265
+ }
1266
+ function anchorLocation(anchor) {
1267
+ switch (anchor.kind) {
1268
+ case "text-range": return anchor.startLine === anchor.endLine ? `line ${String(anchor.startLine)}` : `lines ${String(anchor.startLine)}-${String(anchor.endLine)}`;
1269
+ case "cell": return `${anchor.sheet === void 0 ? "" : `${anchor.sheet}!`}${anchor.location}`;
1270
+ case "slide-element": return `slide ${String(anchor.slide)}: ${anchor.location}`;
1271
+ case "image-rect": return `image ${rectLocation(anchor.rect)}`;
1272
+ case "pdf-page-rect": return `page ${String(anchor.page)}, ${rectLocation(anchor.rect)}`;
1273
+ case "video-time-rect": return `time ${anchor.timeSeconds.toFixed(3)}s${anchor.frame === void 0 ? "" : `, frame ${String(anchor.frame)}`}, ${rectLocation(anchor.rect)}`;
1274
+ case "story-preview-rect": return `story preview ${rectLocation(anchor.rect)}`;
1275
+ case "image-point": return `image point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
1276
+ case "pdf-page-point": return `page ${String(anchor.page)}, point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
1277
+ case "video-time-point": return `time ${anchor.timeSeconds.toFixed(3)}s, point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
1278
+ case "story-preview-point": return `story preview point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
1279
+ }
1280
+ }
1281
+ function storyPreviewProvenance(packet) {
1282
+ const preview = packet.document.storyPreview;
1283
+ if (preview === void 0) return void 0;
1284
+ const sources = preview.sources.map((source) => `- ${source.path} (sha256 ${source.sha256})`).join("\n");
1285
+ return [
1286
+ "Story preview source (edit these sources, never the generated preview artifact):",
1287
+ `Project: ${preview.projectPath}`,
1288
+ `Story: ${preview.storyPath}#${preview.storyExport}`,
1289
+ `Build revision: ${preview.buildRevision}`,
1290
+ `Viewport: ${String(preview.viewport.width)}x${String(preview.viewport.height)}`,
1291
+ "Sources:",
1292
+ sources,
1293
+ "After editing the underlying story or component source, rebuild the preview before treating it as visually verified."
1294
+ ].join("\n");
1295
+ }
1296
+ function authorCaptureContext(packet) {
1297
+ const metadata = JSON.stringify(packet);
1298
+ if (bytes(metadata) > 65536) throw new Error("Author capture packet exceeds 64 KiB.");
1299
+ const annotations = packet.regions.map((region) => `(${String(region.ordinal)}) ${region.comment} [${anchorLocation(region.anchor)}]${region.quote ? `\nQuoted text: ${region.quote}` : ""}`).join("\n\n");
1300
+ const provenance = storyPreviewProvenance(packet);
1301
+ const content = provenance === void 0 ? annotations : `${provenance}\n\nAnnotations:\n${annotations}`;
1302
+ return {
1303
+ kind: "author-capture",
1304
+ source: "author",
1305
+ id: packet.captureId,
1306
+ label: `${packet.regions.length} annotation${packet.regions.length === 1 ? "" : "s"} · ${packet.document.path}`,
1307
+ content,
1308
+ metadata
1309
+ };
1310
+ }
1311
+ function canvasBlob(canvas, type, quality) {
1312
+ return new Promise((resolve, reject) => {
1313
+ canvas.toBlob((blob) => blob === null ? reject(/* @__PURE__ */ new Error("Unable to capture viewport")) : resolve(blob), type, quality);
1314
+ });
1315
+ }
1316
+ async function base64(blob) {
1317
+ const bytes = new Uint8Array(await blob.arrayBuffer());
1318
+ let binary = "";
1319
+ for (let offset = 0; offset < bytes.length; offset += 32768) binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
1320
+ return btoa(binary);
1321
+ }
1322
+ function throwIfAborted(signal) {
1323
+ if (signal?.aborted === true) throw signal.reason;
1324
+ }
1325
+ async function boundedCanvasCapture(canvas) {
1326
+ let mimeType = "image/png";
1327
+ let blob = await canvasBlob(canvas, mimeType);
1328
+ for (const quality of [
1329
+ .9,
1330
+ .75,
1331
+ .6,
1332
+ .45,
1333
+ .3
1334
+ ]) {
1335
+ if (blob.size <= 2097152) break;
1336
+ mimeType = "image/jpeg";
1337
+ blob = await canvasBlob(canvas, mimeType, quality);
1338
+ }
1339
+ if (blob.size > 2097152) throw new Error("Viewport capture exceeds 2 MiB");
1340
+ return {
1341
+ data: await base64(blob),
1342
+ mimeType
1343
+ };
1344
+ }
1345
+ function loadImage(url) {
1346
+ return new Promise((resolve, reject) => {
1347
+ const image = new Image();
1348
+ image.onload = () => resolve(image);
1349
+ image.onerror = () => reject(/* @__PURE__ */ new Error("Unable to load a region capture."));
1350
+ image.src = url;
1351
+ });
1352
+ }
1353
+ function multiRegionCaptureProvider(regions) {
1354
+ return { async capture(signal) {
1355
+ throwIfAborted(signal);
1356
+ if (regions.length === 0 || regions.length > 16) throw new Error("Capture requires between 1 and 16 regions.");
1357
+ const tiles = [];
1358
+ for (const region of regions) {
1359
+ const image = region.thumbnailUrl === void 0 ? void 0 : await loadImage(region.thumbnailUrl);
1360
+ throwIfAborted(signal);
1361
+ const width = image?.naturalWidth ?? region.viewport.width;
1362
+ const height = image?.naturalHeight ?? region.viewport.height;
1363
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) throw new Error("Image is not ready to capture");
1364
+ tiles.push({
1365
+ region,
1366
+ image,
1367
+ width,
1368
+ height
1369
+ });
1370
+ }
1371
+ const width = Math.min(AUTHOR_CAPTURE_MAX_DIMENSION, Math.max(...tiles.map((tile) => tile.width)));
1372
+ const naturalHeight = tiles.reduce((height, tile) => height + tile.height * width / tile.width, 0);
1373
+ const scale = Math.min(1, AUTHOR_CAPTURE_MAX_DIMENSION / Math.max(width, naturalHeight));
1374
+ const canvas = document.createElement("canvas");
1375
+ canvas.width = Math.max(1, Math.round(width * scale));
1376
+ canvas.height = Math.max(1, Math.round(naturalHeight * scale));
1377
+ const context = canvas.getContext("2d");
1378
+ if (context === null) throw new Error("Canvas is unavailable");
1379
+ context.scale(scale, scale);
1380
+ context.fillStyle = "#101216";
1381
+ context.fillRect(0, 0, width, naturalHeight);
1382
+ let y = 0;
1383
+ for (const [index, { region, image, width: sourceWidth, height: sourceHeight }] of tiles.entries()) {
1384
+ const annotationColor = AUTHOR_REGION_COLORS[index % AUTHOR_REGION_COLORS.length];
1385
+ throwIfAborted(signal);
1386
+ const tileHeight = sourceHeight * width / sourceWidth;
1387
+ const drawWidth = width;
1388
+ const drawHeight = tileHeight;
1389
+ const drawX = 0;
1390
+ const drawY = y;
1391
+ let labelX = drawX;
1392
+ let labelY = drawY;
1393
+ if (image !== void 0) {
1394
+ context.drawImage(image, drawX, drawY, drawWidth, drawHeight);
1395
+ if ("rect" in region.anchor) {
1396
+ const rect = region.anchor.rect;
1397
+ const rx = drawX + rect.x * drawWidth;
1398
+ const ry = drawY + rect.y * drawHeight;
1399
+ const rw = rect.width * drawWidth;
1400
+ const rh = rect.height * drawHeight;
1401
+ context.fillStyle = "rgba(8,10,13,0.62)";
1402
+ context.fillRect(drawX, drawY, drawWidth, Math.max(0, ry - drawY));
1403
+ context.fillRect(drawX, ry + rh, drawWidth, Math.max(0, drawY + drawHeight - ry - rh));
1404
+ context.fillRect(drawX, ry, Math.max(0, rx - drawX), rh);
1405
+ context.fillRect(rx + rw, ry, Math.max(0, drawX + drawWidth - rx - rw), rh);
1406
+ context.strokeStyle = annotationColor;
1407
+ context.lineWidth = 3;
1408
+ context.strokeRect(rx, ry, rw, rh);
1409
+ labelX = rx;
1410
+ labelY = ry;
1411
+ } else if ("point" in region.anchor) {
1412
+ const px = drawX + region.anchor.point.x * drawWidth;
1413
+ const py = drawY + region.anchor.point.y * drawHeight;
1414
+ const radius = Math.max(6, Math.min(14, Math.min(drawWidth, drawHeight) / 12));
1415
+ context.beginPath();
1416
+ context.arc(px, py, radius, 0, Math.PI * 2);
1417
+ context.fillStyle = annotationColor;
1418
+ context.fill();
1419
+ context.strokeStyle = annotationForeground(annotationColor);
1420
+ context.lineWidth = 2;
1421
+ context.stroke();
1422
+ labelX = px + radius;
1423
+ labelY = py - radius;
1424
+ }
1425
+ }
1426
+ const ordinal = String(index + 1);
1427
+ let badgeScale = Math.min(1 / scale, drawHeight / 36);
1428
+ context.font = `bold ${String(24 * badgeScale)}px monospace`;
1429
+ const measuredWidth = context.measureText(ordinal).width + 12 * badgeScale;
1430
+ if (measuredWidth > drawWidth) badgeScale *= drawWidth / measuredWidth;
1431
+ context.font = `bold ${String(24 * badgeScale)}px monospace`;
1432
+ context.textBaseline = "top";
1433
+ const padding = 6 * badgeScale;
1434
+ const labelWidth = Math.min(drawWidth, context.measureText(ordinal).width + padding * 2);
1435
+ const labelHeight = 36 * badgeScale;
1436
+ labelX = Math.max(drawX, Math.min(labelX, drawX + drawWidth - labelWidth));
1437
+ labelY = Math.max(drawY, Math.min(labelY, drawY + drawHeight - labelHeight));
1438
+ context.fillStyle = annotationColor;
1439
+ context.fillRect(labelX, labelY, labelWidth, labelHeight);
1440
+ context.fillStyle = annotationForeground(annotationColor);
1441
+ context.fillText(ordinal, labelX + padding, labelY + padding);
1442
+ y += tileHeight;
1443
+ }
1444
+ throwIfAborted(signal);
1445
+ return boundedCanvasCapture(canvas);
1446
+ } };
1447
+ }
1448
+ //#endregion
1449
+ //#region src/extensions/workspaces/sessions/(frontend)/dock/_components/AuthorRegionDrafts.tsx
1450
+ function AuthorRegionDrafts({ sessionId, workspace }) {
1451
+ const [error, setError] = useState();
1452
+ const add = () => {
1453
+ try {
1454
+ commitAuthorRegion(sessionId, workspace.candidateText);
1455
+ setError(void 0);
1456
+ } catch (reason) {
1457
+ setError(reason instanceof Error ? reason.message : String(reason));
1458
+ }
1459
+ };
1460
+ if (!workspace.candidate && workspace.annotations.length === 0) return null;
1461
+ const candidateKind = workspace.candidate?.mode === "point" ? "point" : "region";
1462
+ return /* @__PURE__ */ jsxs("section", {
1463
+ className: "space-y-2",
1464
+ "data-testid": "author-region-drafts",
1465
+ children: [
1466
+ workspace.candidate ? /* @__PURE__ */ jsxs(Fragment, { children: [
1467
+ /* @__PURE__ */ jsxs("h3", {
1468
+ className: "text-base font-semibold text-doom-text",
1469
+ children: ["Current ", candidateKind]
1470
+ }),
1471
+ /* @__PURE__ */ jsxs("p", {
1472
+ className: "text-base text-doom-dim sm:text-sm",
1473
+ children: [
1474
+ "Add feedback to keep this ",
1475
+ candidateKind,
1476
+ " as an unsent annotation."
1477
+ ]
1478
+ }),
1479
+ workspace.candidate.anchor.kind === "video-time-rect" || workspace.candidate.anchor.kind === "video-time-point" ? /* @__PURE__ */ jsxs("p", {
1480
+ className: "text-base text-doom-dim sm:text-sm",
1481
+ children: [
1482
+ "Frame at ",
1483
+ workspace.candidate.anchor.timeSeconds.toFixed(3),
1484
+ "s"
1485
+ ]
1486
+ }) : null,
1487
+ /* @__PURE__ */ jsx("textarea", {
1488
+ "aria-label": "Annotation comment",
1489
+ value: workspace.candidateText,
1490
+ onChange: (event) => setAuthorCandidateText(sessionId, event.target.value),
1491
+ placeholder: "What should change here?",
1492
+ rows: 3,
1493
+ className: "min-h-11 w-full resize-y rounded border border-doom-border bg-doom-deep p-2 text-lg leading-normal text-doom-text focus:border-doom-red focus:outline-none sm:text-sm"
1494
+ }),
1495
+ /* @__PURE__ */ jsx(Button, {
1496
+ className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
1497
+ variant: "outline",
1498
+ onClick: add,
1499
+ disabled: !workspace.candidate || !workspace.candidateText.trim() || workspace.annotations.length >= 16,
1500
+ children: "add annotation"
1501
+ }),
1502
+ /* @__PURE__ */ jsx(Button, {
1503
+ variant: "ghost",
1504
+ className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
1505
+ onClick: () => {
1506
+ setAuthorRegionCandidate(sessionId, void 0);
1507
+ setAuthorCandidateText(sessionId, "");
1508
+ setError(void 0);
1509
+ },
1510
+ children: "Discard annotation"
1511
+ })
1512
+ ] }) : null,
1513
+ error ? /* @__PURE__ */ jsx("output", {
1514
+ className: "block text-base text-doom-red",
1515
+ children: error
1516
+ }) : null,
1517
+ workspace.annotations.length > 0 ? /* @__PURE__ */ jsxs("h3", {
1518
+ className: "text-base font-semibold text-doom-text",
1519
+ children: [
1520
+ "Unsent annotations (",
1521
+ workspace.annotations.length,
1522
+ ")"
1523
+ ]
1524
+ }) : null,
1525
+ /* @__PURE__ */ jsx("ol", {
1526
+ className: "space-y-2",
1527
+ children: workspace.annotations.map((region, index) => /* @__PURE__ */ jsxs("li", {
1528
+ className: "rounded border border-doom-border bg-doom-panel p-2 text-base leading-normal text-doom-text sm:text-sm",
1529
+ children: [
1530
+ /* @__PURE__ */ jsxs("span", { children: [
1531
+ "(",
1532
+ index + 1,
1533
+ ") ",
1534
+ region.mode === "point" ? "Point" : "Region",
1535
+ ": ",
1536
+ region.comment
1537
+ ] }),
1538
+ region.anchor.kind === "video-time-rect" || region.anchor.kind === "video-time-point" ? /* @__PURE__ */ jsxs(Button, {
1539
+ variant: "outline",
1540
+ className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
1541
+ disabled: workspace.candidate !== void 0,
1542
+ onClick: () => {
1543
+ if (region.anchor.kind === "video-time-rect" || region.anchor.kind === "video-time-point") seekAuthorVideo(sessionId, region.anchor.timeSeconds);
1544
+ },
1545
+ children: [
1546
+ "Go to ",
1547
+ region.anchor.timeSeconds.toFixed(3),
1548
+ "s"
1549
+ ]
1550
+ }) : null,
1551
+ /* @__PURE__ */ jsx(Button, {
1552
+ className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
1553
+ variant: "ghost",
1554
+ "aria-label": `Remove annotation ${index + 1}`,
1555
+ onClick: () => removeAuthorRegion(sessionId, region.id),
1556
+ children: "remove"
1557
+ })
1558
+ ]
1559
+ }, region.id))
1560
+ })
1561
+ ]
1562
+ });
1563
+ }
1564
+ //#endregion
1565
+ //#region src/extensions/workspaces/sessions/(frontend)/dock/_components/AuthorRequestLog.tsx
1566
+ const STATUS_LABELS = {
1567
+ REQUESTED: "Request queued",
1568
+ CHANGING: "Agent working",
1569
+ CHANGED: "Change ready",
1570
+ COMPLETE: "Request completed",
1571
+ FAILED: "Request failed",
1572
+ CANCELLED: "Request cancelled"
1573
+ };
1574
+ const STATUS_STYLES = {
1575
+ REQUESTED: "border-doom-yellow/35 bg-doom-yellow/5 text-doom-yellow",
1576
+ CHANGING: "border-doom-magenta/45 bg-doom-magenta/10 text-doom-yellow",
1577
+ CHANGED: "border-doom-cyan/40 bg-doom-cyan/5 text-doom-cyan",
1578
+ COMPLETE: "border-doom-green/40 bg-doom-green/5 text-doom-green",
1579
+ FAILED: "border-doom-red/45 bg-doom-red/10 text-doom-red",
1580
+ CANCELLED: "border-doom-border bg-doom-panel text-doom-dim"
1581
+ };
1582
+ function requestInstruction(requestText) {
1583
+ const referencedContext = requestText.search(/(?:^|\s+)Referenced context "/u);
1584
+ return requestText.slice(0, referencedContext < 0 ? void 0 : referencedContext).replace(/^Please address the attached feedback\.\s*Request ID: \S+\s*/u, "").trim();
1585
+ }
1586
+ function documentName(path) {
1587
+ return path.replaceAll("\\", "/").split("/").at(-1) || path;
1588
+ }
1589
+ function anchorLabel(anchor) {
1590
+ switch (anchor.kind) {
1591
+ case "text-range": return anchor.startLine === anchor.endLine ? `Line ${anchor.startLine}` : `Lines ${anchor.startLine}–${anchor.endLine}`;
1592
+ case "cell": return [anchor.sheet, anchor.location].filter(Boolean).join(" · ");
1593
+ case "slide-element": return `Slide ${anchor.slide} · ${anchor.location}`;
1594
+ case "image-rect": return "Image region";
1595
+ case "pdf-page-rect": return `Page ${anchor.page}`;
1596
+ case "video-time-rect": return `Video ${Math.floor(anchor.timeSeconds / 60)}:${Math.floor(anchor.timeSeconds % 60).toString().padStart(2, "0")}`;
1597
+ case "story-preview-rect": return `${anchor.preview.storyPath}#${anchor.preview.storyExport}`;
1598
+ case "image-point": return "Image point";
1599
+ case "pdf-page-point": return `Page ${anchor.page} point`;
1600
+ case "video-time-point": return `Video ${Math.floor(anchor.timeSeconds / 60)}:${Math.floor(anchor.timeSeconds % 60).toString().padStart(2, "0")} point`;
1601
+ case "story-preview-point": return `${anchor.preview.storyPath}#${anchor.preview.storyExport} point`;
1602
+ }
1603
+ }
1604
+ function progressLabel(request) {
1605
+ const total = request.regions.length;
1606
+ const pending = request.pendingRegions?.length;
1607
+ if (request.status === "CHANGING" && request.currentOperation === "Agent is working on this request") return "Processing request";
1608
+ if (request.status === "CHANGING" && pending !== void 0) return `${total - pending} of ${total} annotations applied`;
1609
+ if (request.status === "CHANGED") return "Changes applied · awaiting save";
1610
+ if (request.status === "COMPLETE") return "Agent finished processing this request";
1611
+ if (request.status === "FAILED") return "Stopped with an error";
1612
+ if (request.status === "CANCELLED") return "Stopped before completion";
1613
+ return `${total} annotation${total === 1 ? "" : "s"} queued`;
1614
+ }
1615
+ function RequestRecord({ request, prominent = false }) {
1616
+ const instruction = requestInstruction(request.requestText);
1617
+ return /* @__PURE__ */ jsxs("article", {
1618
+ "data-testid": prominent ? "author-active-request" : void 0,
1619
+ className: "space-y-3",
1620
+ children: [
1621
+ /* @__PURE__ */ jsxs("header", {
1622
+ className: "border-b border-doom-border-soft pb-3",
1623
+ children: [/* @__PURE__ */ jsx("p", {
1624
+ className: "text-2xs font-bold tracking-widest text-doom-faint",
1625
+ children: "DOCUMENT CONTEXT"
1626
+ }), /* @__PURE__ */ jsxs("p", {
1627
+ className: "mt-2 flex items-center gap-2 truncate text-base font-bold text-doom-hi",
1628
+ children: [/* @__PURE__ */ jsx("span", {
1629
+ "aria-hidden": true,
1630
+ className: "text-doom-magenta",
1631
+ children: "↗"
1632
+ }), documentName(request.documentPath)]
1633
+ })]
1634
+ }),
1635
+ /* @__PURE__ */ jsx("section", {
1636
+ "aria-label": "Request locations",
1637
+ className: "rounded border border-doom-border bg-doom-panel p-3",
1638
+ children: /* @__PURE__ */ jsx("ol", {
1639
+ className: "space-y-3",
1640
+ children: request.regions.map((region, index) => /* @__PURE__ */ jsxs("li", {
1641
+ className: "space-y-2",
1642
+ children: [
1643
+ /* @__PURE__ */ jsxs("p", {
1644
+ className: "font-mono text-2xs font-bold uppercase tracking-wider text-doom-yellow",
1645
+ children: [
1646
+ "WHERE · ",
1647
+ request.regions.length > 1 ? `${index + 1} · ` : "",
1648
+ documentName(request.documentPath),
1649
+ " / ",
1650
+ anchorLabel(region.anchor),
1651
+ " · rev ",
1652
+ request.revision
1653
+ ]
1654
+ }),
1655
+ region.quote ? /* @__PURE__ */ jsxs("blockquote", {
1656
+ className: "line-clamp-3 text-sm italic leading-relaxed text-doom-text",
1657
+ children: [
1658
+ "“",
1659
+ region.quote,
1660
+ "”"
1661
+ ]
1662
+ }) : null,
1663
+ /* @__PURE__ */ jsx("p", {
1664
+ className: "text-xs leading-relaxed text-doom-dim",
1665
+ children: region.comment
1666
+ })
1667
+ ]
1668
+ }, region.id))
1669
+ })
1670
+ }),
1671
+ /* @__PURE__ */ jsxs("section", {
1672
+ "data-testid": prominent ? "author-operation-card" : void 0,
1673
+ className: `rounded border p-3 ${STATUS_STYLES[request.status]}`,
1674
+ children: [
1675
+ /* @__PURE__ */ jsxs("p", {
1676
+ className: "font-mono text-xs font-bold tracking-wider",
1677
+ children: [
1678
+ "●",
1679
+ " ",
1680
+ request.status === "CHANGING" ? "WORKING" : request.status === "COMPLETE" ? "COMPLETED" : request.status === "FAILED" ? "ERROR" : request.status
1681
+ ]
1682
+ }),
1683
+ /* @__PURE__ */ jsx("p", {
1684
+ className: "mt-2 text-sm leading-relaxed text-doom-hi",
1685
+ children: request.currentOperation || STATUS_LABELS[request.status]
1686
+ }),
1687
+ request.error ? /* @__PURE__ */ jsx("p", {
1688
+ className: "mt-2 text-xs leading-relaxed text-doom-red",
1689
+ children: request.error
1690
+ }) : null,
1691
+ request.before !== void 0 || request.after !== void 0 ? /* @__PURE__ */ jsxs("details", {
1692
+ className: "mt-2 border-t border-current/20 pt-2 text-xs text-doom-dim",
1693
+ children: [
1694
+ /* @__PURE__ */ jsx("summary", {
1695
+ className: "cursor-pointer select-none",
1696
+ children: "change preview"
1697
+ }),
1698
+ request.before !== void 0 ? /* @__PURE__ */ jsxs("pre", {
1699
+ className: "mt-2 max-h-28 overflow-auto whitespace-pre-wrap break-words font-mono",
1700
+ children: ["before: ", request.before]
1701
+ }) : null,
1702
+ request.after !== void 0 ? /* @__PURE__ */ jsxs("pre", {
1703
+ className: "mt-2 max-h-28 overflow-auto whitespace-pre-wrap break-words font-mono",
1704
+ children: ["after: ", request.after]
1705
+ }) : null
1706
+ ]
1707
+ }) : null
1708
+ ]
1709
+ }),
1710
+ /* @__PURE__ */ jsxs("section", {
1711
+ className: "space-y-2",
1712
+ children: [/* @__PURE__ */ jsx("p", {
1713
+ className: "text-2xs font-bold tracking-widest text-doom-faint",
1714
+ children: "REQUEST · HOW TO CHANGE"
1715
+ }), /* @__PURE__ */ jsx("div", {
1716
+ className: "rounded border border-doom-border bg-doom-deep p-3",
1717
+ children: /* @__PURE__ */ jsx("p", {
1718
+ className: "whitespace-pre-wrap text-sm leading-relaxed text-doom-text",
1719
+ children: instruction || "No additional instruction."
1720
+ })
1721
+ })]
1722
+ }),
1723
+ /* @__PURE__ */ jsx("footer", {
1724
+ className: "font-mono text-2xs text-doom-dim",
1725
+ children: progressLabel(request)
1726
+ })
1727
+ ]
1728
+ });
1729
+ }
1730
+ function AuthorRequestLog({ requests }) {
1731
+ const latest = requests.at(-1);
1732
+ const earlier = requests.slice(0, -1).reverse();
1733
+ return /* @__PURE__ */ jsxs("section", {
1734
+ "data-testid": "author-request-history",
1735
+ className: "space-y-3",
1736
+ children: [
1737
+ /* @__PURE__ */ jsx("h3", {
1738
+ className: "text-2xs font-bold tracking-widest text-doom-faint",
1739
+ children: "AUTHORING"
1740
+ }),
1741
+ latest === void 0 ? /* @__PURE__ */ jsx("p", {
1742
+ className: "text-sm leading-relaxed text-doom-dim",
1743
+ children: "No requests or changes yet."
1744
+ }) : /* @__PURE__ */ jsx(RequestRecord, {
1745
+ request: latest,
1746
+ prominent: true
1747
+ }),
1748
+ earlier.length > 0 ? /* @__PURE__ */ jsxs("details", {
1749
+ className: "border-t border-doom-border-soft pt-3",
1750
+ "data-testid": "author-earlier-requests",
1751
+ children: [/* @__PURE__ */ jsxs("summary", {
1752
+ className: "cursor-pointer select-none text-2xs font-bold tracking-wider text-doom-faint",
1753
+ children: [
1754
+ "EARLIER REQUESTS (",
1755
+ earlier.length,
1756
+ ")"
1757
+ ]
1758
+ }), /* @__PURE__ */ jsx("ol", {
1759
+ className: "mt-3 space-y-5",
1760
+ children: earlier.map((request) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(RequestRecord, { request }) }, request.id))
1761
+ })]
1762
+ }) : null
1763
+ ]
1764
+ });
1765
+ }
1766
+ //#endregion
1767
+ //#region src/extensions/workspaces/sessions/(frontend)/dock/_components/AuthorToolPalette.tsx
1768
+ function AuthorToolPalette({ sessionId, kind, activeTool }) {
1769
+ const [error, setError] = useState();
1770
+ const format = (style) => {
1771
+ const selected = authorSessionWorkspace(sessionId).candidate;
1772
+ const document = selected && authorDocument(sessionId, selected.documentPath);
1773
+ if (!selected || !document || selected.anchor.kind !== "text-range" || selected.revision !== document.version || selected.sourceSha256 !== document.sourceSha256) {
1774
+ setError("Select text in the document first.");
1775
+ return;
1776
+ }
1777
+ const { startOffset, endOffset } = selected.anchor;
1778
+ const content = document.content ?? "";
1779
+ const text = content.slice(startOffset, endOffset);
1780
+ const replacement = style === "Bold" ? `**${text}**` : style === "Link" ? `[${text}](https://)` : text.split("\n").map((line) => `${style === "Heading" ? "## " : "- "}${line}`).join("\n");
1781
+ reviseAuthorDocument(sessionId, document.path, content.slice(0, startOffset) + replacement + content.slice(endOffset));
1782
+ setAuthorRegionCandidate(sessionId, void 0);
1783
+ setError(void 0);
1784
+ };
1785
+ const tools = [
1786
+ {
1787
+ label: "Region",
1788
+ glyph: "⌗",
1789
+ action: () => setAuthorToolMode(sessionId, activeTool === "mark" ? "select" : "mark"),
1790
+ active: activeTool === "mark"
1791
+ },
1792
+ ...kind === "markdown" ? [
1793
+ {
1794
+ label: "Bold",
1795
+ glyph: "B",
1796
+ action: () => format("Bold")
1797
+ },
1798
+ {
1799
+ label: "Heading",
1800
+ glyph: "H",
1801
+ action: () => format("Heading")
1802
+ },
1803
+ {
1804
+ label: "Link",
1805
+ glyph: "↗",
1806
+ action: () => format("Link")
1807
+ },
1808
+ {
1809
+ label: "List",
1810
+ glyph: "≡",
1811
+ action: () => format("List")
1812
+ }
1813
+ ] : [],
1814
+ {
1815
+ label: "Comment",
1816
+ glyph: "●",
1817
+ action: () => setAuthorToolMode(sessionId, activeTool === "comment" ? "select" : "comment"),
1818
+ active: activeTool === "comment"
1819
+ }
1820
+ ];
1821
+ return /* @__PURE__ */ jsxs("section", {
1822
+ "data-testid": "author-tool-palette",
1823
+ className: "space-y-2 border-b border-doom-border-soft pb-3",
1824
+ children: [
1825
+ /* @__PURE__ */ jsxs("div", {
1826
+ className: "flex justify-between text-sm text-doom-dim",
1827
+ children: [/* @__PURE__ */ jsx("h3", {
1828
+ className: "font-bold tracking-widest",
1829
+ children: "TOOLS"
1830
+ }), /* @__PURE__ */ jsx("span", { children: "focused tab" })]
1831
+ }),
1832
+ /* @__PURE__ */ jsx("div", {
1833
+ className: "grid grid-cols-3 gap-1.5",
1834
+ children: tools.map((tool) => /* @__PURE__ */ jsxs(Button, {
1835
+ size: "md",
1836
+ variant: "outline",
1837
+ "aria-label": tool.label === "Region" ? "mark region" : tool.label,
1838
+ "aria-pressed": tool.active,
1839
+ onClick: tool.action,
1840
+ className: `min-h-11 min-w-11 gap-1.5 rounded border text-base [@media(pointer:fine)]:min-h-8 sm:text-sm ${tool.active ? "border-doom-red bg-doom-red/10 text-doom-red hover:bg-doom-red/15" : "border-doom-border bg-doom-panel text-doom-dim"}`,
1841
+ children: [/* @__PURE__ */ jsx("span", {
1842
+ "aria-hidden": "true",
1843
+ children: tool.glyph
1844
+ }), tool.label]
1845
+ }, tool.label))
1846
+ }),
1847
+ /* @__PURE__ */ jsx("p", {
1848
+ className: "text-base leading-normal text-doom-dim sm:text-sm",
1849
+ children: activeTool === "mark" ? "Drag over the document to mark a region." : activeTool === "comment" ? "Click the document to place a comment pin." : "Choose Region to drag an area, or Comment to place a point."
1850
+ }),
1851
+ error ? /* @__PURE__ */ jsx("output", {
1852
+ className: "block text-base text-doom-red",
1853
+ children: error
1854
+ }) : null
1855
+ ]
1856
+ });
1857
+ }
1858
+ //#endregion
1859
+ //#region src/extensions/workspaces/sessions/(frontend)/dock/_components/AuthorPanel.tsx
1860
+ function AuthorPanel({ sessionId, activeMinorModes, submitCapture, statuses, renderSlot, renderSessionActivity }) {
1861
+ const [captureStatus, setCaptureStatus] = useState();
1862
+ const [capturing, setCapturing] = useState(false);
1863
+ const documents = useStore(authorWorkspace.store, (state) => {
1864
+ if (sessionId === null) return [];
1865
+ const prefix = `${sessionId}\n`;
1866
+ return Object.entries(state.documents).filter(([key]) => key.startsWith(prefix)).map(([, document]) => document);
1867
+ });
1868
+ const workspace = useStore(authorWorkspace.store, (state) => sessionId === null ? void 0 : state.sessions[sessionId]);
1869
+ const grid = useStore(authorGrid.store, (state) => sessionId === null ? void 0 : state.sessions[sessionId]);
1870
+ const focused = documents.find((document) => document.path === workspace?.focusedDocument?.path);
1871
+ const activity = sessionId === null ? null : renderSessionActivity?.() ?? null;
1872
+ if (!activeMinorModes?.includes("author")) return null;
1873
+ return /* @__PURE__ */ jsxs("section", {
1874
+ "data-testid": "author-panel",
1875
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden",
1876
+ children: [/* @__PURE__ */ jsx("div", {
1877
+ className: "min-h-0 flex-1 overflow-y-auto p-3",
1878
+ children: /* @__PURE__ */ jsxs("div", {
1879
+ className: "flex flex-col gap-2",
1880
+ children: [
1881
+ /* @__PURE__ */ jsx("div", {
1882
+ "data-testid": "author-preview-providers",
1883
+ children: renderSlot("author.preview-provider")
1884
+ }),
1885
+ sessionId !== null && workspace !== void 0 && focused !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1886
+ /* @__PURE__ */ jsx("h2", {
1887
+ className: "text-base font-semibold text-doom-text",
1888
+ children: "Annotations"
1889
+ }),
1890
+ /* @__PURE__ */ jsx("p", {
1891
+ className: "text-base leading-normal text-doom-dim sm:text-sm",
1892
+ children: "Drag a region or place a point, add feedback, then submit here. Requests queue while the agent is working."
1893
+ }),
1894
+ focused.kind === "video" ? /* @__PURE__ */ jsx("p", {
1895
+ className: "text-base text-doom-dim sm:text-sm",
1896
+ children: "Annotations reference video frames. They do not save changes to the source video."
1897
+ }) : null,
1898
+ /* @__PURE__ */ jsx(AuthorToolPalette, {
1899
+ sessionId,
1900
+ kind: focused.kind,
1901
+ activeTool: workspace.activeTool
1902
+ }),
1903
+ autonomousVoiceGridVisible(statuses) && grid !== void 0 ? /* @__PURE__ */ jsxs("div", {
1904
+ "data-testid": "author-grid-snapshot",
1905
+ className: "rounded border border-doom-red/40 bg-doom-red/5 p-2 text-base text-doom-dim",
1906
+ children: [/* @__PURE__ */ jsx("strong", {
1907
+ className: "text-doom-red",
1908
+ children: "VOICE GRID A1–H8"
1909
+ }), /* @__PURE__ */ jsxs("p", {
1910
+ className: "mt-1 truncate",
1911
+ children: ["token ", grid.geometryToken]
1912
+ })]
1913
+ }) : null,
1914
+ /* @__PURE__ */ jsx(AuthorRegionDrafts, {
1915
+ sessionId,
1916
+ workspace
1917
+ }, focused.path),
1918
+ workspace.annotations.length > 0 ? /* @__PURE__ */ jsxs("div", {
1919
+ className: "space-y-1.5 border-b border-doom-border-soft pb-3",
1920
+ children: [/* @__PURE__ */ jsx(Button, {
1921
+ className: "min-h-11 min-w-11 w-full text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
1922
+ variant: "outline",
1923
+ "data-testid": "author-attach-capture",
1924
+ disabled: capturing || workspace.candidate !== void 0 || submitCapture === void 0,
1925
+ onClick: async () => {
1926
+ if (capturing || workspace.candidate || submitCapture === void 0) return;
1927
+ setCapturing(true);
1928
+ setCaptureStatus("Submitting annotations…");
1929
+ try {
1930
+ const packet = createAuthorCapturePacket(crypto.randomUUID(), Date.now(), focused, workspace.annotations);
1931
+ await submitCapture({
1932
+ ...await multiRegionCaptureProvider(workspace.annotations).capture(),
1933
+ context: authorCaptureContext(packet)
1934
+ });
1935
+ setCaptureStatus("Request submitted. You can keep annotating here.");
1936
+ } catch (reason) {
1937
+ setCaptureStatus(reason instanceof Error ? reason.message : String(reason));
1938
+ } finally {
1939
+ setCapturing(false);
1940
+ }
1941
+ },
1942
+ children: capturing ? "Submitting…" : `Submit ${workspace.annotations.length} annotation${workspace.annotations.length === 1 ? "" : "s"}`
1943
+ }), workspace.candidate ? /* @__PURE__ */ jsx("p", {
1944
+ className: "text-base text-doom-dim sm:text-sm",
1945
+ children: "Add or discard the current annotation before submitting."
1946
+ }) : null]
1947
+ }) : null,
1948
+ submitCapture === void 0 ? /* @__PURE__ */ jsx("output", {
1949
+ className: "text-sm text-doom-red",
1950
+ children: "Submission unavailable. Reload to update the app."
1951
+ }) : null,
1952
+ captureStatus ? /* @__PURE__ */ jsx("output", {
1953
+ className: "block text-sm text-doom-dim",
1954
+ children: captureStatus
1955
+ }) : null
1956
+ ] }) : null,
1957
+ /* @__PURE__ */ jsx(AuthorRequestLog, { requests: workspace?.requests ?? [] })
1958
+ ]
1959
+ })
1960
+ }), activity === null ? null : /* @__PURE__ */ jsx("footer", {
1961
+ "data-testid": "author-session-activity",
1962
+ className: "shrink-0 border-t border-doom-border bg-doom-rail p-3",
1963
+ children: activity
1964
+ })]
1965
+ });
1966
+ }
1967
+ //#endregion
1968
+ //#region src/extensions/workspaces/sessions/(frontend)/dock/authoring.web.tsx
1969
+ var authoring_web_default = defineDockFace({
1970
+ id: "authoring",
1971
+ label: "authoring",
1972
+ order: 30,
1973
+ autoSelect: true,
1974
+ panel: AuthorPanel,
1975
+ requiredMinorMode: "author",
1976
+ visibility: {
1977
+ subscribe() {
1978
+ return () => void 0;
1979
+ },
1980
+ isVisible(sessionId) {
1981
+ return sessionId !== null;
1982
+ }
1983
+ }
1984
+ });
1985
+ //#endregion
1986
+ //#region src/types/authorPreview.ts
1987
+ function parseAuthorPreviewAction(input) {
1988
+ if (typeof input !== "object" || input === null) return null;
1989
+ const candidate = input;
1990
+ if (candidate.version !== 1 || typeof candidate.label !== "string" || candidate.label.trim() === "") return null;
1991
+ if (candidate.detail !== void 0 && typeof candidate.detail !== "string") return null;
1992
+ if (typeof candidate.createTab !== "function") return null;
1993
+ if (candidate.supportsSource !== void 0 && typeof candidate.supportsSource !== "function") return null;
1994
+ if (candidate.embeddedPanel !== void 0 && typeof candidate.embeddedPanel !== "function") return null;
1995
+ return candidate;
1996
+ }
1997
+ const authorPreviewActionSlot = {
1998
+ slot: "author.preview-action",
1999
+ parse: parseAuthorPreviewAction
2000
+ };
2001
+ //#endregion
2002
+ //#region src/types/apiRoutes.ts
2003
+ /**
2004
+ * This package's routes, as data.
2005
+ *
2006
+ * No scope and no base path. The build reads both off
2007
+ * `src/extensions/workspaces/sessions/(backend)/api/author/`, so the mount is
2008
+ * stated once, by the folder that creates it. The browser used to restate it:
2009
+ * `authorStateUrl` built one URL from `API_BASE_PATH` and the document helper
2010
+ * two files away spelled `author` out again as a literal, so the two could
2011
+ * disagree and nothing would say so.
2012
+ *
2013
+ * The order below is the order the three Hono apps register in: `/state` in
2014
+ * `authorApi`, the eight bridge routes in `authorBridgeApi`, the three document
2015
+ * routes in `authorDocumentApi`. A reviewer can read the two side by side.
2016
+ *
2017
+ * Response types are named only where a caller reads the body. The bridge
2018
+ * routes are called by the hub's author channel over `requestSessionApi`, which
2019
+ * has its own message vocabulary and never sees this client.
2020
+ */
2021
+ var apiRoutes_default = defineApiRoutes({
2022
+ state: {
2023
+ method: "GET",
2024
+ path: "/state",
2025
+ response: apiResponse()
2026
+ },
2027
+ bridgeRegister: {
2028
+ method: "POST",
2029
+ path: "/bridge/register"
2030
+ },
2031
+ bridgeCatalog: {
2032
+ method: "POST",
2033
+ path: "/bridge/catalog"
2034
+ },
2035
+ /** Long-polls for the next capability request, so it settles when one arrives. */
2036
+ bridgeNext: {
2037
+ method: "POST",
2038
+ path: "/bridge/next"
2039
+ },
2040
+ bridgeResult: {
2041
+ method: "POST",
2042
+ path: "/bridge/result"
2043
+ },
2044
+ bridgeCancelled: {
2045
+ method: "POST",
2046
+ path: "/bridge/cancelled"
2047
+ },
2048
+ bridgeDisconnect: {
2049
+ method: "POST",
2050
+ path: "/bridge/disconnect"
2051
+ },
2052
+ bridgeDescribe: {
2053
+ method: "GET",
2054
+ path: "/bridge/describe"
2055
+ },
2056
+ bridgeInvoke: {
2057
+ method: "POST",
2058
+ path: "/bridge/invoke"
2059
+ },
2060
+ /**
2061
+ * Parses one structured document.
2062
+ *
2063
+ * Answers a byte-length summary instead when the request names no format,
2064
+ * which is the agent-side tool path; the page always names one.
2065
+ */
2066
+ documentsOpen: {
2067
+ method: "POST",
2068
+ path: "/documents/open",
2069
+ query: ["session"],
2070
+ response: apiResponse()
2071
+ },
2072
+ documentsPreflight: {
2073
+ method: "POST",
2074
+ path: "/documents/preflight",
2075
+ query: ["session"],
2076
+ response: apiResponse()
2077
+ },
2078
+ documentsSerialize: {
2079
+ method: "POST",
2080
+ path: "/documents/serialize",
2081
+ query: ["session"],
2082
+ response: apiResponse()
2083
+ },
2084
+ /**
2085
+ * The host's own bytes route, not this package's.
2086
+ *
2087
+ * `host: true` hangs it off the session root rather than under `/plugins`,
2088
+ * which is how the cockpit already serves it. Author reads a document's
2089
+ * bytes and its digest through it, and writes the edited bytes back to the
2090
+ * same path, so both stay raw transfers rather than calls: the body is the
2091
+ * file and the digest travels in a header.
2092
+ */
2093
+ file: {
2094
+ method: "GET",
2095
+ path: "/file",
2096
+ query: ["path"],
2097
+ host: true
2098
+ }
2099
+ });
2100
+ const api = createApiClient(apiRoutes_default, {
2101
+ scopes: ["session"],
2102
+ basePath: "author",
2103
+ transport: (input, init) => sealedTransport.fetch(input, init),
2104
+ sessionAddress: sessionApiAddress
2105
+ });
2106
+ //#endregion
2107
+ //#region src/types/authorApi.ts
2108
+ /** Query parameter naming the session a document request belongs to. */
2109
+ const SESSION_QUERY_PARAM = "session";
2110
+ //#endregion
2111
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/authorFiles.ts
2112
+ const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
2113
+ "avif",
2114
+ "bmp",
2115
+ "gif",
2116
+ "jpeg",
2117
+ "jpg",
2118
+ "png",
2119
+ "svg",
2120
+ "webp"
2121
+ ]);
2122
+ const VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
2123
+ "m4v",
2124
+ "mov",
2125
+ "mp4",
2126
+ "webm"
2127
+ ]);
2128
+ function structuredFormat(path) {
2129
+ const lower = path.toLowerCase();
2130
+ if (lower.endsWith(".slides.md")) return "markdown-slides";
2131
+ if (lower.endsWith(".csv")) return "csv";
2132
+ if (lower.endsWith(".pptx")) return "pptx";
2133
+ if (lower.endsWith(".xlsx")) return "xlsx";
2134
+ }
2135
+ function authorKindForPath(path) {
2136
+ const lower = path.toLowerCase();
2137
+ if (/\.stories\.(?:js|jsx|ts|tsx)$/u.test(lower)) return "story-preview";
2138
+ const format = structuredFormat(path);
2139
+ if (format === "markdown-slides") return "slides";
2140
+ if (format !== void 0) return format;
2141
+ const extension = lower.split(".").at(-1) ?? "";
2142
+ if (extension === "md") return "markdown";
2143
+ if (IMAGE_EXTENSIONS.has(extension)) return "image";
2144
+ if (VIDEO_EXTENSIONS.has(extension)) return "video";
2145
+ if (extension === "pdf") return "pdf";
2146
+ if ([
2147
+ "txt",
2148
+ "json",
2149
+ "yaml",
2150
+ "yml",
2151
+ "xml",
2152
+ "html",
2153
+ "css",
2154
+ "js",
2155
+ "ts",
2156
+ "tsx",
2157
+ "jsx"
2158
+ ].includes(extension)) return "text";
2159
+ return "opaque";
2160
+ }
2161
+ /**
2162
+ * Where a document's bytes are read from and written back to.
2163
+ *
2164
+ * This is the host's route, not this package's, which is why the route table
2165
+ * marks it `host: true` and it lands beside the session rather than under
2166
+ * `/plugins`. It stays a raw transfer rather than a call: the body is the file
2167
+ * itself, and the digest travels in a header on both legs.
2168
+ */
2169
+ function authorSessionFileUrl(sessionId, path) {
2170
+ return api.session(sessionId).file.url({ query: { path } });
2171
+ }
2172
+ /** One document route, with the refusal the reader is shown: the route's own words, or its status. */
2173
+ async function documentRequest(method, sessionId, body, signal) {
2174
+ const result = await method({
2175
+ body,
2176
+ query: { [SESSION_QUERY_PARAM]: sessionId },
2177
+ ...signal === void 0 ? {} : { signal }
2178
+ });
2179
+ if (!result.ok) throw new Error(result.error === "" ? `Author document API failed (${result.status})` : result.error);
2180
+ return result.data;
2181
+ }
2182
+ async function loadAuthorDocument(sessionId, path, signal) {
2183
+ const kind = authorKindForPath(path);
2184
+ const url = authorSessionFileUrl(sessionId, path);
2185
+ const response = await sealedTransport.fetch(url, { signal });
2186
+ if (!response.ok) throw new Error(`Author could not open ${path} (${response.status})`);
2187
+ const sourceSha256 = response.headers.get("X-File-SHA256") ?? void 0;
2188
+ const format = structuredFormat(path);
2189
+ if (format !== void 0) {
2190
+ const parsed = await documentRequest(api.session(sessionId).documentsOpen, sessionId, {
2191
+ path,
2192
+ format
2193
+ }, signal);
2194
+ const parsedDigest = parsed.manifest?.sourceDigest;
2195
+ if (sourceSha256 !== void 0 && parsedDigest !== void 0 && sourceSha256 !== parsedDigest) throw new Error("Document source changed while opening. Reopen the document.");
2196
+ const csvDialect = format === "csv" ? csvDialectFromMetadata(parsed.manifest?.metadata) : void 0;
2197
+ return {
2198
+ path,
2199
+ kind,
2200
+ title: path.split("/").at(-1) ?? path,
2201
+ structuredFormat: format,
2202
+ fragments: parsed.fragments,
2203
+ ...csvDialect === void 0 ? {} : { csvDialect },
2204
+ originalFragments: parsed.fragments.map((fragment) => ({ ...fragment })),
2205
+ ...sourceSha256 === void 0 ? {} : { sourceSha256 }
2206
+ };
2207
+ }
2208
+ const mediaUrl = kind === "image" || kind === "video" || kind === "pdf" || kind === "opaque" ? url : void 0;
2209
+ const content = kind === "text" || kind === "markdown" || kind === "story-preview" ? await response.text() : void 0;
2210
+ return {
2211
+ path,
2212
+ kind,
2213
+ title: path.split("/").at(-1) ?? path,
2214
+ ...content === void 0 ? {} : { content },
2215
+ ...mediaUrl === void 0 ? {} : { mediaUrl },
2216
+ ...sourceSha256 === void 0 ? {} : { sourceSha256 }
2217
+ };
2218
+ }
2219
+ function operationsFor(document) {
2220
+ const originals = new Map((document.originalFragments ?? []).map((fragment) => [fragment.id, fragment.text]));
2221
+ return (document.fragments ?? []).filter((fragment) => !fragment.readOnly && originals.get(fragment.id) !== fragment.text).map((fragment) => ({
2222
+ fragmentId: fragment.id,
2223
+ replacement: fragment.text
2224
+ }));
2225
+ }
2226
+ function decodeBase64(value) {
2227
+ if (!/^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$/.test(value)) throw new Error("Invalid serialized document bytes.");
2228
+ const binary = atob(value);
2229
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
2230
+ }
2231
+ function imageMimeType(path) {
2232
+ const extension = path.split(".").at(-1)?.toLowerCase();
2233
+ if (extension === "png") return "image/png";
2234
+ if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
2235
+ if (extension === "webp") return "image/webp";
2236
+ }
2237
+ async function croppedImageBytes(documentInput, signal) {
2238
+ const { crop, mediaUrl } = documentInput;
2239
+ if (crop === void 0 || mediaUrl === void 0) throw new Error("This Author image has no crop source.");
2240
+ const mimeType = imageMimeType(documentInput.path);
2241
+ if (mimeType === void 0) throw new Error("This image format cannot preserve its encoding after crop.");
2242
+ const response = await sealedTransport.fetch(mediaUrl, {
2243
+ signal,
2244
+ cache: "no-store"
2245
+ });
2246
+ if (!response.ok) throw new Error(`Author could not read the image before saving (${response.status})`);
2247
+ const bitmap = await createImageBitmap(await response.blob());
2248
+ try {
2249
+ const sourceX = Math.round(crop.x * bitmap.width);
2250
+ const sourceY = Math.round(crop.y * bitmap.height);
2251
+ const sourceWidth = Math.max(1, Math.round(crop.width * bitmap.width));
2252
+ const sourceHeight = Math.max(1, Math.round(crop.height * bitmap.height));
2253
+ const canvas = window.document.createElement("canvas");
2254
+ canvas.width = sourceWidth;
2255
+ canvas.height = sourceHeight;
2256
+ const context = canvas.getContext("2d");
2257
+ if (context === null) throw new Error("Canvas is unavailable for image crop.");
2258
+ context.drawImage(bitmap, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, sourceWidth, sourceHeight);
2259
+ const blob = await new Promise((resolve) => canvas.toBlob(resolve, mimeType));
2260
+ if (blob === null || blob.type !== mimeType) throw new Error("The browser cannot encode this cropped image format.");
2261
+ return blob;
2262
+ } finally {
2263
+ bitmap.close();
2264
+ }
2265
+ }
2266
+ async function bytesToSave(sessionId, document, signal) {
2267
+ if (document.kind === "text" || document.kind === "markdown" || document.kind === "story-preview") return document.content ?? "";
2268
+ if (document.kind === "image" && document.crop !== void 0) return await croppedImageBytes(document, signal);
2269
+ if (document.structuredFormat === void 0) throw new Error("This Author view cannot be saved.");
2270
+ const operations = operationsFor(document);
2271
+ const request = {
2272
+ path: document.path,
2273
+ format: document.structuredFormat,
2274
+ operations,
2275
+ ...document.csvDialect === void 0 ? {} : { csvDialect: document.csvDialect }
2276
+ };
2277
+ const preflight = await documentRequest(api.session(sessionId).documentsPreflight, sessionId, request, signal);
2278
+ if (preflight.sourceDigest !== void 0 && preflight.sourceDigest !== document.sourceSha256) throw new Error("Document source changed before saving. Reopen the document.");
2279
+ if (!preflight.accepted) throw new Error(preflight.issues.map((issue) => issue.message).join("\n") || "Author preflight rejected the edit.");
2280
+ const serialized = await documentRequest(api.session(sessionId).documentsSerialize, sessionId, {
2281
+ ...request,
2282
+ preflightDigest: preflight.digest
2283
+ }, signal);
2284
+ if (serialized.encoding !== "base64") throw new Error("Author serialize returned an unsupported encoding.");
2285
+ const bytes = decodeBase64(serialized.bytes);
2286
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
2287
+ }
2288
+ async function saveAuthorDocument(sessionId, document, signal) {
2289
+ if (document.sourceSha256 === void 0) throw new Error("This Author view has no source digest.");
2290
+ const response = await sealedTransport.fetch(authorSessionFileUrl(sessionId, document.path), {
2291
+ method: "PUT",
2292
+ headers: {
2293
+ "Content-Type": "application/octet-stream",
2294
+ "X-Expected-SHA256": document.sourceSha256
2295
+ },
2296
+ body: await bytesToSave(sessionId, document, signal),
2297
+ signal
2298
+ });
2299
+ if (!response.ok) throw new Error(`Author save failed (${response.status})`);
2300
+ const sha256 = response.headers.get("X-File-SHA256");
2301
+ if (sha256 === null || !/^[a-f0-9]{64}$/.test(sha256)) throw new Error("Author save returned an invalid digest.");
2302
+ return sha256;
2303
+ }
2304
+ function csvDialectFromMetadata(metadata) {
2305
+ if (metadata === void 0) return void 0;
2306
+ const { delimiter, quote, recordDelimiter } = metadata;
2307
+ if (delimiter !== "," && delimiter !== ";" && delimiter !== " " && delimiter !== "|" || quote !== "\"" && quote !== "'" || recordDelimiter !== "\n" && recordDelimiter !== "\r\n") throw new Error("Author document API returned an invalid CSV dialect.");
2308
+ return {
2309
+ delimiter,
2310
+ quote,
2311
+ recordDelimiter
2312
+ };
2313
+ }
2314
+ //#endregion
2315
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/authorGridTools.ts
2316
+ const stringProperty = {
2317
+ type: "string",
2318
+ minLength: 1
2319
+ };
2320
+ function inputRecord(input) {
2321
+ if (typeof input !== "object" || input === null || Array.isArray(input)) throw new Error("Expected an object.");
2322
+ return input;
2323
+ }
2324
+ function requiredString(value, name) {
2325
+ if (typeof value[name] !== "string" || value[name] === "") throw new Error(`${name} must be a non-empty string.`);
2326
+ return value[name];
2327
+ }
2328
+ function resolvedAnchor(sessionId, path, kind, cell) {
2329
+ if (authorDocument(sessionId, path) === void 0) throw new Error("STALE_DOCUMENT: The focused Author document is no longer open.");
2330
+ if (kind === "pdf" || kind === "video" || kind === "opaque") throw new Error("UNSUPPORTED_GRID: PDF and video coordinates are capture-only and require a visible native selection.");
2331
+ return resolveAuthorGridNativeAnchor(sessionId, cell);
2332
+ }
2333
+ function checkedRegion(sessionId, path, value) {
2334
+ const document = authorDocument(sessionId, path);
2335
+ if (document === void 0) throw new Error("STALE_DOCUMENT: The Author document is no longer open.");
2336
+ if (document.savingVersion !== void 0) throw new Error("SAVE_IN_PROGRESS: Wait for the current Author save to finish.");
2337
+ const expectedRevision = value.expectedRevision;
2338
+ const expectedSourceSha256 = requiredString(value, "expectedSourceSha256");
2339
+ if (expectedRevision !== document.version || expectedSourceSha256 !== document.sourceSha256) throw new Error("STALE_DOCUMENT: The Author revision or source digest changed.");
2340
+ const regionId = requiredString(value, "regionId");
2341
+ const workspace = authorSessionWorkspace(sessionId);
2342
+ const region = workspace.regions.find((candidate) => candidate.id === regionId) ?? workspace.requests.filter((request) => request.status === "REQUESTED" || request.status === "CHANGING").flatMap((request) => request.pendingRegions ?? request.regions).find((candidate) => candidate.id === regionId);
2343
+ if (region === void 0 || region.documentPath !== path) throw new Error("STALE_REGION: The resolved Author region is unavailable.");
2344
+ if (region.revision !== document.version || region.sourceSha256 !== document.sourceSha256) throw new Error("STALE_REGION: The resolved Author region no longer matches the document.");
2345
+ return {
2346
+ document,
2347
+ region
2348
+ };
2349
+ }
2350
+ function gridTools(sessionId, path, kind) {
2351
+ return [{
2352
+ name: "author_describe_grid",
2353
+ label: "Describe grid",
2354
+ description: "Describe the current A1 through H8 grid and return its geometry token.",
2355
+ inputSchema: {
2356
+ type: "object",
2357
+ additionalProperties: false,
2358
+ properties: {}
2359
+ },
2360
+ execute: async () => {
2361
+ const geometry = authorGridGeometry(sessionId);
2362
+ const document = authorDocument(sessionId, path);
2363
+ if (geometry === void 0 || document === void 0 || geometry.documentPath !== path || geometry.revision !== document.version) throw new Error("STALE_GRID: The visible Author grid is unavailable.");
2364
+ return {
2365
+ columns: "A-H",
2366
+ rows: "1-8",
2367
+ ...geometry
2368
+ };
2369
+ }
2370
+ }, {
2371
+ name: "author_resolve_grid_cell",
2372
+ label: "Resolve grid cell",
2373
+ description: "Resolve one current grid cell to a native document anchor before mutation.",
2374
+ inputSchema: {
2375
+ type: "object",
2376
+ additionalProperties: false,
2377
+ required: [
2378
+ "cell",
2379
+ "geometryToken",
2380
+ "instruction"
2381
+ ],
2382
+ properties: {
2383
+ cell: stringProperty,
2384
+ geometryToken: stringProperty,
2385
+ instruction: stringProperty
2386
+ }
2387
+ },
2388
+ execute: async (input) => {
2389
+ const value = inputRecord(input);
2390
+ const instruction = requiredString(value, "instruction");
2391
+ const cell = resolveAuthorGridCell(sessionId, requiredString(value, "cell"), requiredString(value, "geometryToken"));
2392
+ const geometry = authorGridGeometry(sessionId);
2393
+ const resolved = resolvedAnchor(sessionId, path, kind, cell);
2394
+ const regionId = crypto.randomUUID();
2395
+ const createdAt = Date.now();
2396
+ const region = {
2397
+ id: regionId,
2398
+ documentPath: path,
2399
+ revision: geometry.revision,
2400
+ sourceSha256: geometry.sourceSha256,
2401
+ comment: instruction,
2402
+ ...resolved,
2403
+ viewport: { ...geometry.viewport },
2404
+ voiceGrid: cell.evidence,
2405
+ createdAt
2406
+ };
2407
+ addAuthorRegion(sessionId, region);
2408
+ putAuthorRequest(sessionId, {
2409
+ id: `voice:${regionId}`,
2410
+ documentPath: path,
2411
+ requestText: instruction,
2412
+ regions: [region],
2413
+ pendingRegions: [region],
2414
+ status: "REQUESTED",
2415
+ createdAt,
2416
+ updatedAt: createdAt,
2417
+ revision: geometry.revision,
2418
+ sourceSha256: geometry.sourceSha256
2419
+ });
2420
+ return {
2421
+ regionId,
2422
+ anchor: resolved.anchor,
2423
+ quote: resolved.quote,
2424
+ evidence: cell.evidence
2425
+ };
2426
+ }
2427
+ }];
2428
+ }
2429
+ function assertRebasable(pending, changed) {
2430
+ const remaining = pending.filter((region) => region.id !== changed.id);
2431
+ if (changed.anchor.kind === "image-rect" && remaining.length > 0) throw new Error("UNSUPPORTED_REGION: A multi-region image request cannot apply more than one crop.");
2432
+ if (changed.anchor.kind !== "text-range") return;
2433
+ for (const region of remaining) {
2434
+ if (region.anchor.kind !== "text-range") continue;
2435
+ if (!(region.anchor.endOffset <= changed.anchor.startOffset || region.anchor.startOffset >= changed.anchor.endOffset)) throw new Error("AMBIGUOUS_REGION: Submitted text regions overlap and cannot be safely rebased.");
2436
+ }
2437
+ }
2438
+ function lineAtOffset(content, offset) {
2439
+ return content.slice(0, Math.max(0, offset)).split("\n").length;
2440
+ }
2441
+ function rebasePendingRegions(pending, changed, result, nextContent) {
2442
+ return pending.filter((region) => region.id !== changed.id).map((region) => {
2443
+ if (changed.anchor.kind !== "text-range" || region.anchor.kind !== "text-range" || nextContent === void 0) return {
2444
+ ...region,
2445
+ revision: result.revision
2446
+ };
2447
+ const delta = (result.after?.length ?? 0) - (result.before?.length ?? 0);
2448
+ const shift = region.anchor.startOffset >= changed.anchor.endOffset ? delta : 0;
2449
+ const startOffset = region.anchor.startOffset + shift;
2450
+ const endOffset = region.anchor.endOffset + shift;
2451
+ return {
2452
+ ...region,
2453
+ revision: result.revision,
2454
+ anchor: {
2455
+ ...region.anchor,
2456
+ startOffset,
2457
+ endOffset,
2458
+ startLine: lineAtOffset(nextContent, startOffset),
2459
+ endLine: lineAtOffset(nextContent, endOffset)
2460
+ }
2461
+ };
2462
+ });
2463
+ }
2464
+ function mutationTool(sessionId, path, kind) {
2465
+ if (kind === "pdf" || kind === "video" || kind === "opaque") return void 0;
2466
+ const needsReplacement = kind !== "image";
2467
+ return {
2468
+ name: "author_apply_region",
2469
+ label: "Apply region change",
2470
+ description: "Apply a fenced mutation through a previously resolved native Author region.",
2471
+ inputSchema: {
2472
+ type: "object",
2473
+ additionalProperties: false,
2474
+ required: [
2475
+ "regionId",
2476
+ "expectedRevision",
2477
+ "expectedSourceSha256",
2478
+ ...needsReplacement ? ["replacement"] : []
2479
+ ],
2480
+ properties: {
2481
+ regionId: stringProperty,
2482
+ expectedRevision: {
2483
+ type: "integer",
2484
+ minimum: 0
2485
+ },
2486
+ expectedSourceSha256: stringProperty,
2487
+ ...needsReplacement ? { replacement: { type: "string" } } : {}
2488
+ }
2489
+ },
2490
+ execute: async (input, signal) => {
2491
+ const value = inputRecord(input);
2492
+ const regionId = requiredString(value, "regionId");
2493
+ const request = authorSessionWorkspace(sessionId).requests.find((candidate) => (candidate.pendingRegions ?? candidate.regions).some((item) => item.id === regionId) && candidate.status === "REQUESTED");
2494
+ if (request !== void 0) updateAuthorRequest(sessionId, request.id, (current) => ({
2495
+ ...current,
2496
+ status: "CHANGING",
2497
+ currentOperation: "validating native anchor",
2498
+ updatedAt: Date.now()
2499
+ }));
2500
+ try {
2501
+ if (signal.aborted) throw signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("Author mutation cancelled.");
2502
+ const { document, region } = checkedRegion(sessionId, path, value);
2503
+ const pending = request?.pendingRegions ?? request?.regions ?? [region];
2504
+ assertRebasable(pending, region);
2505
+ if (request !== void 0) updateAuthorRequest(sessionId, request.id, (current) => ({
2506
+ ...current,
2507
+ currentOperation: `mutating ${region.anchor.kind}`,
2508
+ updatedAt: Date.now()
2509
+ }));
2510
+ let result;
2511
+ let nextContent;
2512
+ if (region.anchor.kind === "text-range") {
2513
+ const replacement = typeof value.replacement === "string" ? value.replacement : "";
2514
+ const { startOffset, endOffset } = region.anchor;
2515
+ const content = document.content ?? "";
2516
+ const before = content.slice(startOffset, endOffset);
2517
+ nextContent = content.slice(0, startOffset) + replacement + content.slice(endOffset);
2518
+ reviseAuthorDocument(sessionId, path, nextContent);
2519
+ result = {
2520
+ changed: true,
2521
+ before,
2522
+ after: replacement,
2523
+ revision: document.version + 1
2524
+ };
2525
+ } else if (region.anchor.kind === "cell" || region.anchor.kind === "slide-element") {
2526
+ const fragmentId = region.anchor.fragmentId;
2527
+ const fragment = document.fragments?.find((candidate) => candidate.id === fragmentId);
2528
+ if (fragment === void 0 || fragment.readOnly === true) throw new Error("UNSUPPORTED_REGION: The native fragment is not editable.");
2529
+ const replacement = typeof value.replacement === "string" ? value.replacement : "";
2530
+ reviseAuthorFragment(sessionId, path, fragment.id, replacement);
2531
+ result = {
2532
+ changed: true,
2533
+ before: fragment.text,
2534
+ after: replacement,
2535
+ revision: document.version + 1
2536
+ };
2537
+ } else if (region.anchor.kind === "image-rect") {
2538
+ setAuthorCrop(sessionId, path, region.anchor.rect);
2539
+ result = {
2540
+ changed: true,
2541
+ crop: region.anchor.rect,
2542
+ revision: document.version + 1
2543
+ };
2544
+ } else throw new Error("UNSUPPORTED_REGION: PDF and video regions are capture-only.");
2545
+ if (request !== void 0) {
2546
+ const pendingRegions = rebasePendingRegions(pending, region, result, nextContent);
2547
+ updateAuthorRequest(sessionId, request.id, (current) => ({
2548
+ ...current,
2549
+ status: pendingRegions.length === 0 ? "CHANGED" : "REQUESTED",
2550
+ currentOperation: void 0,
2551
+ before: [current.before, result.before].filter((item) => item !== void 0).join("\n") || void 0,
2552
+ after: [current.after, result.after ?? (result.crop === void 0 ? void 0 : JSON.stringify(result.crop))].filter((item) => item !== void 0).join("\n") || void 0,
2553
+ pendingRegions,
2554
+ revision: result.revision,
2555
+ updatedAt: Date.now()
2556
+ }));
2557
+ }
2558
+ return result;
2559
+ } catch (error) {
2560
+ if (request !== void 0) updateAuthorRequest(sessionId, request.id, (current) => ({
2561
+ ...current,
2562
+ status: signal.aborted ? "CANCELLED" : "FAILED",
2563
+ currentOperation: void 0,
2564
+ error: signal.aborted ? "Author mutation cancelled." : error instanceof Error ? error.message : String(error),
2565
+ updatedAt: Date.now()
2566
+ }));
2567
+ throw error;
2568
+ }
2569
+ }
2570
+ };
2571
+ }
2572
+ function authorGridTools(sessionId, path, kind) {
2573
+ const mutation = mutationTool(sessionId, path, kind);
2574
+ return [...gridTools(sessionId, path, kind), ...mutation === void 0 ? [] : [mutation]];
2575
+ }
2576
+ //#endregion
2577
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/authorProfiles.ts
2578
+ function record(input) {
2579
+ if (typeof input !== "object" || input === null || Array.isArray(input)) throw new Error("Expected an object");
2580
+ return input;
2581
+ }
2582
+ function text(value, field) {
2583
+ if (typeof value !== "string") throw new Error(`${field} must be a string`);
2584
+ return value;
2585
+ }
2586
+ function target(input) {
2587
+ const value = record(input);
2588
+ return {
2589
+ sessionId: text(value.sessionId, "sessionId"),
2590
+ path: text(value.path, "path"),
2591
+ value
2592
+ };
2593
+ }
2594
+ const TARGET_PROPERTIES = {
2595
+ sessionId: {
2596
+ type: "string",
2597
+ minLength: 1
2598
+ },
2599
+ path: {
2600
+ type: "string",
2601
+ minLength: 1
2602
+ }
2603
+ };
2604
+ const AUTHOR_TEXT_PROFILE = {
2605
+ id: AUTHOR_RUNTIME_BINDING_IDS.text,
2606
+ tools: [{
2607
+ name: "author_add_comment",
2608
+ label: "Add comment",
2609
+ description: "Add a local review comment to one open Author document.",
2610
+ inputSchema: {
2611
+ type: "object",
2612
+ additionalProperties: false,
2613
+ required: [
2614
+ "sessionId",
2615
+ "path",
2616
+ "body"
2617
+ ],
2618
+ properties: {
2619
+ ...TARGET_PROPERTIES,
2620
+ body: { type: "string" },
2621
+ quote: { type: "string" }
2622
+ }
2623
+ },
2624
+ execute: async (input) => {
2625
+ const { sessionId, path, value } = target(input);
2626
+ const body = text(value.body, "body");
2627
+ addAuthorAnnotation(sessionId, path, {
2628
+ id: `${Date.now()}:${body.length}`,
2629
+ kind: "comment",
2630
+ body,
2631
+ ...typeof value.quote === "string" ? { quote: value.quote } : {}
2632
+ });
2633
+ return { added: true };
2634
+ }
2635
+ }, {
2636
+ name: "author_add_highlight",
2637
+ label: "Add highlight",
2638
+ description: "Highlight a line range in one open Author text document.",
2639
+ inputSchema: {
2640
+ type: "object",
2641
+ additionalProperties: false,
2642
+ required: [
2643
+ "sessionId",
2644
+ "path",
2645
+ "startLine",
2646
+ "endLine"
2647
+ ],
2648
+ properties: {
2649
+ ...TARGET_PROPERTIES,
2650
+ startLine: {
2651
+ type: "integer",
2652
+ minimum: 1
2653
+ },
2654
+ endLine: {
2655
+ type: "integer",
2656
+ minimum: 1
2657
+ }
2658
+ }
2659
+ },
2660
+ execute: async (input) => {
2661
+ const { sessionId, path, value } = target(input);
2662
+ if (!Number.isSafeInteger(value.startLine) || !Number.isSafeInteger(value.endLine)) throw new Error("Highlight lines must be integers");
2663
+ const startLine = value.startLine;
2664
+ const endLine = value.endLine;
2665
+ if (startLine < 1 || endLine < startLine) throw new Error("Invalid highlight range");
2666
+ addAuthorAnnotation(sessionId, path, {
2667
+ id: `${Date.now()}:${startLine}:${endLine}`,
2668
+ kind: "highlight",
2669
+ body: "",
2670
+ startLine,
2671
+ endLine
2672
+ });
2673
+ return { added: true };
2674
+ }
2675
+ }]
2676
+ };
2677
+ const AUTHOR_MEDIA_PROFILE = {
2678
+ id: AUTHOR_RUNTIME_BINDING_IDS.media,
2679
+ tools: []
2680
+ };
2681
+ function bindProfile(profile, sessionId, path) {
2682
+ return {
2683
+ id: profile.id,
2684
+ tools: profile.tools.map((tool) => {
2685
+ const schema = tool.inputSchema;
2686
+ const { sessionId: _sessionId, path: _path, ...boundProperties } = record(schema.properties);
2687
+ const required = Array.isArray(schema.required) ? schema.required.filter((entry) => entry !== "sessionId" && entry !== "path") : [];
2688
+ return {
2689
+ ...tool,
2690
+ inputSchema: {
2691
+ ...schema,
2692
+ properties: boundProperties,
2693
+ required
2694
+ },
2695
+ execute: async (input, signal) => await tool.execute({
2696
+ ...record(input),
2697
+ sessionId,
2698
+ path
2699
+ }, signal)
2700
+ };
2701
+ })
2702
+ };
2703
+ }
2704
+ function authorProfilesForDocument(sessionId, path, kind) {
2705
+ const profiles = kind === "image" ? [bindProfile(AUTHOR_MEDIA_PROFILE, sessionId, path)] : kind === "text" || kind === "markdown" ? [bindProfile(AUTHOR_TEXT_PROFILE, sessionId, path)] : [];
2706
+ const tools = authorGridTools(sessionId, path, kind);
2707
+ if (tools.length === 0) return profiles;
2708
+ return profiles.length === 0 ? [{
2709
+ id: AUTHOR_RUNTIME_BINDING_IDS.media,
2710
+ tools
2711
+ }] : [{
2712
+ ...profiles[0],
2713
+ tools: [...profiles[0].tools, ...tools]
2714
+ }];
2715
+ }
2716
+ //#endregion
2717
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/authorMedia.ts
2718
+ /**
2719
+ * Native media elements cannot perform the remote session's encrypted HTTP exchange.
2720
+ *
2721
+ * `source` addresses the host's own session file route, which the route table
2722
+ * declares as `file` with `host: true`; this package serves no bytes of its
2723
+ * own. The transport stays raw because what comes back is a blob, and a call
2724
+ * through the generated client reads the body as JSON to report a refusal.
2725
+ */
2726
+ async function loadAuthorMedia(source, signal) {
2727
+ const response = await sealedTransport.fetch(source, {
2728
+ signal,
2729
+ cache: "no-store"
2730
+ });
2731
+ if (!response.ok) throw new Error(`Author media could not be loaded (${response.status}).`);
2732
+ const blob = await response.blob();
2733
+ signal.throwIfAborted();
2734
+ const url = URL.createObjectURL(blob);
2735
+ let disposed = false;
2736
+ return {
2737
+ url,
2738
+ dispose() {
2739
+ if (disposed) return;
2740
+ disposed = true;
2741
+ URL.revokeObjectURL(url);
2742
+ }
2743
+ };
2744
+ }
2745
+ //#endregion
2746
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/authorRegions.ts
2747
+ /** Clamp a drag to the source element, including drags made in reverse. */
2748
+ function normalizedAuthorRectangle(bounds, drag) {
2749
+ const width = bounds.right - bounds.left;
2750
+ const height = bounds.bottom - bounds.top;
2751
+ if (![...Object.values(bounds), ...Object.values(drag)].every(Number.isFinite) || width <= 0 || height <= 0) return null;
2752
+ const left = Math.max(bounds.left, Math.min(drag.left, drag.right));
2753
+ const right = Math.min(bounds.right, Math.max(drag.left, drag.right));
2754
+ const top = Math.max(bounds.top, Math.min(drag.top, drag.bottom));
2755
+ const bottom = Math.min(bounds.bottom, Math.max(drag.top, drag.bottom));
2756
+ if (right <= left || bottom <= top) return null;
2757
+ return {
2758
+ x: (left - bounds.left) / width,
2759
+ y: (top - bounds.top) / height,
2760
+ width: (right - left) / width,
2761
+ height: (bottom - top) / height
2762
+ };
2763
+ }
2764
+ //#endregion
2765
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorVideoControls.tsx
2766
+ function videoTimeLabel(seconds) {
2767
+ const milliseconds = Math.max(0, Math.round((Number.isFinite(seconds) ? seconds : 0) * 1e3));
2768
+ return `${String(Math.floor(milliseconds / 6e4)).padStart(2, "0")}:${String(Math.floor(milliseconds / 1e3) % 60).padStart(2, "0")}.${String(milliseconds % 1e3).padStart(3, "0")}`;
2769
+ }
2770
+ function AuthorVideoControls({ playback, ready, locked, marking, onSeek, onToggle, onAnnotate }) {
2771
+ const [seconds, setSeconds] = useState("");
2772
+ const duration = playback.duration;
2773
+ const invalid = seconds.trim() === "" || !Number.isFinite(Number(seconds)) || Number(seconds) < 0 || Number(seconds) > duration;
2774
+ const buttonClass = "min-h-11 px-3 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm";
2775
+ return /* @__PURE__ */ jsxs("section", {
2776
+ "aria-label": "Video annotation controls",
2777
+ className: "mt-2 space-y-2 rounded border border-doom-border bg-doom-panel p-2.5 text-base text-doom-text sm:text-sm",
2778
+ children: [
2779
+ /* @__PURE__ */ jsxs("div", {
2780
+ className: "flex flex-wrap items-center justify-between gap-3",
2781
+ children: [/* @__PURE__ */ jsxs("output", {
2782
+ "aria-label": "Playback position",
2783
+ className: "font-mono text-base tabular-nums",
2784
+ children: [
2785
+ videoTimeLabel(playback.currentTime),
2786
+ " / ",
2787
+ videoTimeLabel(duration)
2788
+ ]
2789
+ }), /* @__PURE__ */ jsx("span", {
2790
+ className: "text-doom-dim",
2791
+ children: locked ? "Annotation draft: add or discard it before seeking." : marking ? "Frame paused. Drag a region on the video." : "Seek to a moment, then choose Annotate this frame."
2792
+ })]
2793
+ }),
2794
+ /* @__PURE__ */ jsx("input", {
2795
+ "aria-label": "Video timeline",
2796
+ type: "range",
2797
+ min: 0,
2798
+ max: duration || 0,
2799
+ step: .001,
2800
+ value: Math.min(playback.currentTime, duration),
2801
+ disabled: locked || duration <= 0,
2802
+ onChange: (event) => onSeek(Number(event.target.value)),
2803
+ className: "h-11 w-full cursor-pointer accent-doom-blue [@media(pointer:fine)]:h-6"
2804
+ }),
2805
+ /* @__PURE__ */ jsxs("div", {
2806
+ className: "flex flex-wrap items-center gap-2",
2807
+ children: [
2808
+ /* @__PURE__ */ jsx(Button, {
2809
+ className: buttonClass,
2810
+ disabled: locked || duration <= 0,
2811
+ onClick: onToggle,
2812
+ children: playback.playing ? "Pause" : "Play"
2813
+ }),
2814
+ /* @__PURE__ */ jsx(Button, {
2815
+ className: buttonClass,
2816
+ disabled: locked || duration <= 0,
2817
+ onClick: () => onSeek(Math.max(0, playback.currentTime - .1)),
2818
+ "aria-label": "Seek back 0.1 seconds",
2819
+ children: "−0.1s"
2820
+ }),
2821
+ /* @__PURE__ */ jsx(Button, {
2822
+ className: buttonClass,
2823
+ disabled: locked || duration <= 0,
2824
+ onClick: () => onSeek(Math.min(duration, playback.currentTime + .1)),
2825
+ "aria-label": "Seek forward 0.1 seconds",
2826
+ children: "+0.1s"
2827
+ }),
2828
+ /* @__PURE__ */ jsx(Button, {
2829
+ variant: "primary",
2830
+ className: buttonClass,
2831
+ disabled: locked || !ready,
2832
+ onClick: onAnnotate,
2833
+ children: "Annotate this frame"
2834
+ }),
2835
+ /* @__PURE__ */ jsxs("form", {
2836
+ className: "flex flex-wrap items-center gap-2 sm:ml-auto",
2837
+ onSubmit: (event) => {
2838
+ event.preventDefault();
2839
+ if (!invalid && !locked) onSeek(Number(seconds));
2840
+ },
2841
+ children: [
2842
+ /* @__PURE__ */ jsx("label", {
2843
+ htmlFor: "author-video-seconds",
2844
+ className: "text-doom-dim",
2845
+ children: "Go to seconds"
2846
+ }),
2847
+ /* @__PURE__ */ jsx("input", {
2848
+ id: "author-video-seconds",
2849
+ type: "number",
2850
+ min: 0,
2851
+ max: duration,
2852
+ step: .001,
2853
+ value: seconds,
2854
+ onChange: (event) => setSeconds(event.target.value),
2855
+ disabled: locked || duration <= 0,
2856
+ placeholder: "1.250",
2857
+ className: "h-11 w-24 rounded border border-doom-border bg-doom-deep px-2 text-lg [@media(pointer:fine)]:h-8 sm:text-sm"
2858
+ }),
2859
+ /* @__PURE__ */ jsx(Button, {
2860
+ type: "submit",
2861
+ className: buttonClass,
2862
+ disabled: locked || duration <= 0 || invalid,
2863
+ children: "Go"
2864
+ })
2865
+ ]
2866
+ })
2867
+ ]
2868
+ })
2869
+ ]
2870
+ });
2871
+ }
2872
+ //#endregion
2873
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorMediaView.tsx
2874
+ /** Marks source media. Native playback and page controls remain usable in select mode. */
2875
+ function AuthorMediaView({ sessionId, document: source, activeTool, displayedRegions, pendingCandidate = false, seekRequest }) {
2876
+ const host = useRef(null);
2877
+ const image = useRef(null);
2878
+ const video = useRef(null);
2879
+ const pdf = useRef(null);
2880
+ const start = useRef(void 0);
2881
+ const [playback, setPlayback] = useState({
2882
+ playing: false,
2883
+ currentTime: 0,
2884
+ duration: 0
2885
+ });
2886
+ const [frameReady, setFrameReady] = useState(false);
2887
+ const [selection, setSelection] = useState();
2888
+ const [error, setError] = useState();
2889
+ const [media, setMedia] = useState();
2890
+ const mediaUrl = media?.source === source.mediaUrl ? media?.url : void 0;
2891
+ useEffect(() => {
2892
+ if (source.mediaUrl === void 0) return;
2893
+ const input = source.mediaUrl;
2894
+ const controller = new AbortController();
2895
+ let dispose;
2896
+ setError(void 0);
2897
+ loadAuthorMedia(input, controller.signal).then((asset) => {
2898
+ if (controller.signal.aborted) {
2899
+ asset.dispose();
2900
+ return;
2901
+ }
2902
+ dispose = () => asset.dispose();
2903
+ setMedia({
2904
+ source: input,
2905
+ url: asset.url
2906
+ });
2907
+ }).catch((cause) => {
2908
+ if (!controller.signal.aborted) setError(cause instanceof Error ? cause.message : String(cause));
2909
+ });
2910
+ return () => {
2911
+ controller.abort();
2912
+ dispose?.();
2913
+ };
2914
+ }, [source.mediaUrl]);
2915
+ const seek = (seconds) => {
2916
+ if (authorSessionWorkspace(sessionId).candidate || !Number.isFinite(seconds)) return;
2917
+ const player = video.current;
2918
+ if (!player) return;
2919
+ player.pause();
2920
+ setAuthorToolMode(sessionId, "select");
2921
+ setSelection(void 0);
2922
+ if (player.getState().currentTime === seconds) return;
2923
+ setFrameReady(false);
2924
+ player.seek(seconds);
2925
+ };
2926
+ useEffect(() => {
2927
+ if (seekRequest?.path === source.path && seekRequest.generation === authorSessionWorkspace(sessionId).focusedDocument?.generation) seek(seekRequest.timeSeconds);
2928
+ }, [seekRequest]);
2929
+ useEffect(() => {
2930
+ if (!pendingCandidate && start.current === void 0) setSelection(void 0);
2931
+ }, [pendingCandidate]);
2932
+ useEffect(() => registerAuthorGridResolver(sessionId, (cell, geometry) => {
2933
+ const target = image.current;
2934
+ if (source.kind !== "image" || target === null || target.naturalWidth === 0 || target.naturalHeight === 0) return void 0;
2935
+ const { originX = 0, originY = 0, width, height } = geometry.viewport;
2936
+ const cellBounds = {
2937
+ left: originX + cell.rect.x * width,
2938
+ top: originY + cell.rect.y * height,
2939
+ right: originX + (cell.rect.x + cell.rect.width) * width,
2940
+ bottom: originY + (cell.rect.y + cell.rect.height) * height
2941
+ };
2942
+ const bounds = target.getBoundingClientRect();
2943
+ const left = Math.max(bounds.left, cellBounds.left);
2944
+ const top = Math.max(bounds.top, cellBounds.top);
2945
+ const right = Math.min(bounds.right, cellBounds.right);
2946
+ const bottom = Math.min(bounds.bottom, cellBounds.bottom);
2947
+ if (right <= left || bottom <= top || bounds.width <= 0 || bounds.height <= 0) return void 0;
2948
+ return { anchor: {
2949
+ kind: "image-rect",
2950
+ rect: {
2951
+ x: (left - bounds.left) / bounds.width,
2952
+ y: (top - bounds.top) / bounds.height,
2953
+ width: (right - left) / bounds.width,
2954
+ height: (bottom - top) / bounds.height
2955
+ },
2956
+ naturalWidth: target.naturalWidth,
2957
+ naturalHeight: target.naturalHeight
2958
+ } };
2959
+ }), [
2960
+ sessionId,
2961
+ source.kind,
2962
+ source.path,
2963
+ source.version
2964
+ ]);
2965
+ const mark = async (event) => {
2966
+ const drag = start.current;
2967
+ start.current = void 0;
2968
+ if (!drag) return;
2969
+ const bounds = drag.element.getBoundingClientRect();
2970
+ if (bounds.width <= 0 || bounds.height <= 0) return;
2971
+ const point = {
2972
+ x: Math.min(1, Math.max(0, (event.clientX - bounds.left) / bounds.width)),
2973
+ y: Math.min(1, Math.max(0, (event.clientY - bounds.top) / bounds.height))
2974
+ };
2975
+ const rect = drag.mode === "mark" ? normalizedAuthorRectangle(bounds, {
2976
+ left: drag.x,
2977
+ top: drag.y,
2978
+ right: event.clientX,
2979
+ bottom: event.clientY
2980
+ }) : void 0;
2981
+ if (drag.mode === "mark" && !rect) return;
2982
+ setSelection(drag.mode === "mark" ? {
2983
+ path: source.path,
2984
+ rect
2985
+ } : {
2986
+ path: source.path,
2987
+ point
2988
+ });
2989
+ const focused = authorSessionWorkspace(sessionId).focusedDocument;
2990
+ if (!focused || focused.path !== source.path) return;
2991
+ let anchor;
2992
+ let blob = null;
2993
+ let thumbnailUrl;
2994
+ try {
2995
+ if (drag.element instanceof HTMLImageElement) {
2996
+ const image = drag.element;
2997
+ if (!image.naturalWidth || !image.naturalHeight) throw new Error("Image is not ready.");
2998
+ anchor = drag.mode === "mark" ? {
2999
+ kind: "image-rect",
3000
+ rect,
3001
+ naturalWidth: image.naturalWidth,
3002
+ naturalHeight: image.naturalHeight
3003
+ } : {
3004
+ kind: "image-point",
3005
+ point,
3006
+ naturalWidth: image.naturalWidth,
3007
+ naturalHeight: image.naturalHeight
3008
+ };
3009
+ const canvas = document.createElement("canvas");
3010
+ const scale = Math.min(1, 1600 / Math.max(image.naturalWidth, image.naturalHeight));
3011
+ canvas.width = Math.max(1, Math.round(image.naturalWidth * scale));
3012
+ canvas.height = Math.max(1, Math.round(image.naturalHeight * scale));
3013
+ const context = canvas.getContext("2d");
3014
+ if (!context) throw new Error("Canvas is unavailable.");
3015
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
3016
+ blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
3017
+ } else if (source.kind === "pdf") {
3018
+ const page = pdf.current?.getState();
3019
+ if (!page?.sourceWidth) throw new Error("PDF page is not ready.");
3020
+ anchor = drag.mode === "mark" ? {
3021
+ kind: "pdf-page-rect",
3022
+ page: page.page,
3023
+ rect
3024
+ } : {
3025
+ kind: "pdf-page-point",
3026
+ page: page.page,
3027
+ point
3028
+ };
3029
+ blob = await pdf.current.capturePage();
3030
+ } else {
3031
+ const frame = await video.current?.captureFrame();
3032
+ if (!frame) throw new Error("Video frame is not ready.");
3033
+ anchor = drag.mode === "mark" ? {
3034
+ kind: "video-time-rect",
3035
+ rect,
3036
+ timeSeconds: frame.timeSeconds,
3037
+ intrinsicWidth: frame.width,
3038
+ intrinsicHeight: frame.height
3039
+ } : {
3040
+ kind: "video-time-point",
3041
+ point,
3042
+ timeSeconds: frame.timeSeconds,
3043
+ intrinsicWidth: frame.width,
3044
+ intrinsicHeight: frame.height
3045
+ };
3046
+ blob = frame.blob;
3047
+ }
3048
+ if (!blob) throw new Error("Unable to capture selected media.");
3049
+ if (authorSessionWorkspace(sessionId).focusedDocument?.generation !== focused.generation) return;
3050
+ thumbnailUrl = URL.createObjectURL(blob);
3051
+ setAuthorRegionCandidate(sessionId, {
3052
+ documentPath: source.path,
3053
+ revision: source.version,
3054
+ sourceSha256: source.sourceSha256,
3055
+ mode: drag.mode === "mark" ? "region" : "point",
3056
+ anchor,
3057
+ viewport: {
3058
+ width: bounds.width,
3059
+ height: bounds.height
3060
+ },
3061
+ thumbnailUrl,
3062
+ evidence: blob,
3063
+ createdAt: Date.now()
3064
+ });
3065
+ setError(void 0);
3066
+ } catch (reason) {
3067
+ if (thumbnailUrl) URL.revokeObjectURL(thumbnailUrl);
3068
+ setError(reason instanceof Error ? reason.message : String(reason));
3069
+ }
3070
+ };
3071
+ const selected = selection?.path === source.path ? selection.rect : void 0;
3072
+ const selectedPoint = selection?.path === source.path ? selection.point : void 0;
3073
+ const selectionOverlay = selected === void 0 ? null : /* @__PURE__ */ jsx("div", {
3074
+ "data-testid": "author-media-selection",
3075
+ className: "pointer-events-none absolute border-2 border-doom-red bg-doom-red/10",
3076
+ style: {
3077
+ left: `${selected.x * 100}%`,
3078
+ top: `${selected.y * 100}%`,
3079
+ width: `${selected.width * 100}%`,
3080
+ height: `${selected.height * 100}%`
3081
+ }
3082
+ });
3083
+ const pointPin = (ordinal, point, key) => /* @__PURE__ */ jsx("span", {
3084
+ "data-author-point": ordinal,
3085
+ "data-testid": ordinal === void 0 ? "author-media-point-selection" : void 0,
3086
+ className: `pointer-events-none absolute z-10 flex h-5 min-w-5 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border-2 px-1 text-2xs font-bold ${ordinal === void 0 ? "border-white bg-doom-red text-white" : "border-doom-deep bg-doom-yellow text-doom-deep"}`,
3087
+ style: {
3088
+ left: `${String(point.x * 100)}%`,
3089
+ top: `${String(point.y * 100)}%`
3090
+ },
3091
+ children: ordinal ?? "•"
3092
+ }, key);
3093
+ const pointSelectionOverlay = selectedPoint === void 0 ? null : pointPin(void 0, selectedPoint, "selection");
3094
+ return /* @__PURE__ */ jsxs("div", {
3095
+ ref: host,
3096
+ className: "min-h-0 flex-1 overflow-auto p-4",
3097
+ style: {
3098
+ touchAction: activeTool === "select" ? "auto" : "none",
3099
+ cursor: activeTool === "select" ? void 0 : "crosshair"
3100
+ },
3101
+ onPointerMove: (event) => {
3102
+ const drag = start.current;
3103
+ if (drag === void 0 || drag.mode !== "mark") return;
3104
+ const rect = normalizedAuthorRectangle(drag.element.getBoundingClientRect(), {
3105
+ left: drag.x,
3106
+ top: drag.y,
3107
+ right: event.clientX,
3108
+ bottom: event.clientY
3109
+ });
3110
+ setSelection(rect === null ? void 0 : {
3111
+ path: source.path,
3112
+ rect
3113
+ });
3114
+ },
3115
+ onPointerDownCapture: (event) => {
3116
+ if (activeTool !== "mark" && activeTool !== "comment" || event.button !== 0 || pendingCandidate) return;
3117
+ const element = event.target;
3118
+ if (!(element instanceof HTMLImageElement || element instanceof HTMLVideoElement || element instanceof HTMLCanvasElement)) return;
3119
+ event.preventDefault();
3120
+ setSelection(void 0);
3121
+ if (element instanceof HTMLVideoElement) {
3122
+ if (video.current?.isFrameReady?.() === false) {
3123
+ setError("Wait for the selected frame to finish loading.");
3124
+ return;
3125
+ }
3126
+ video.current?.pause();
3127
+ }
3128
+ start.current = {
3129
+ x: event.clientX,
3130
+ y: event.clientY,
3131
+ mode: activeTool,
3132
+ element
3133
+ };
3134
+ event.currentTarget.setPointerCapture(event.pointerId);
3135
+ },
3136
+ onPointerUp: (event) => void mark(event),
3137
+ onPointerCancel: () => {
3138
+ start.current = void 0;
3139
+ setSelection(void 0);
3140
+ },
3141
+ children: [
3142
+ mediaUrl === void 0 ? /* @__PURE__ */ jsx("p", {
3143
+ className: "text-doom-dim",
3144
+ children: error === void 0 ? "Loading media..." : "Media unavailable."
3145
+ }) : source.kind === "image" ? /* @__PURE__ */ jsxs("div", {
3146
+ className: "relative inline-block max-w-full",
3147
+ children: [
3148
+ /* @__PURE__ */ jsx("img", {
3149
+ ref: image,
3150
+ src: mediaUrl,
3151
+ alt: source.path,
3152
+ draggable: false,
3153
+ className: "block max-w-full"
3154
+ }),
3155
+ selectionOverlay,
3156
+ pointSelectionOverlay,
3157
+ displayedRegions.flatMap(({ ordinal, region }) => {
3158
+ if (region.anchor.kind === "image-point") return [pointPin(ordinal, region.anchor.point, region.id)];
3159
+ if (region.anchor.kind !== "image-rect") return [];
3160
+ const { rect } = region.anchor;
3161
+ return [/* @__PURE__ */ jsx("div", {
3162
+ "data-author-region": ordinal,
3163
+ className: "pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10",
3164
+ style: {
3165
+ left: `${String(rect.x * 100)}%`,
3166
+ top: `${String(rect.y * 100)}%`,
3167
+ width: `${String(rect.width * 100)}%`,
3168
+ height: `${String(rect.height * 100)}%`
3169
+ },
3170
+ children: /* @__PURE__ */ jsx("span", {
3171
+ className: "absolute -left-2 -top-2 flex h-4 min-w-4 items-center justify-center rounded-full bg-doom-yellow px-1 text-2xs font-bold text-doom-deep",
3172
+ children: ordinal
3173
+ })
3174
+ }, region.id)];
3175
+ })
3176
+ ]
3177
+ }) : /* @__PURE__ */ jsxs("div", {
3178
+ className: "relative w-fit max-w-full",
3179
+ children: [
3180
+ /* @__PURE__ */ jsx(MediaPreview, {
3181
+ src: mediaUrl,
3182
+ path: source.path,
3183
+ kind: source.kind === "pdf" ? "pdf" : source.kind === "video" ? "video" : "download",
3184
+ controllerRef: video,
3185
+ pdfControllerRef: pdf,
3186
+ "data-testid": "author-media",
3187
+ videoControls: false,
3188
+ onPlaybackStateChange: (state) => {
3189
+ setPlayback(state);
3190
+ setFrameReady(video.current?.isFrameReady() ?? false);
3191
+ if (state.playing) setSelection(void 0);
3192
+ }
3193
+ }),
3194
+ selectionOverlay,
3195
+ pointSelectionOverlay,
3196
+ displayedRegions.map(({ ordinal, region }) => {
3197
+ if (source.kind === "pdf") {
3198
+ if (region.anchor.kind !== "pdf-page-point" && region.anchor.kind !== "pdf-page-rect") return null;
3199
+ if (region.anchor.page !== pdf.current?.getState().page) return null;
3200
+ if (region.anchor.kind === "pdf-page-point") return pointPin(ordinal, region.anchor.point, region.id);
3201
+ const { rect } = region.anchor;
3202
+ return /* @__PURE__ */ jsx("div", {
3203
+ "data-author-region": ordinal,
3204
+ className: "pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10",
3205
+ style: {
3206
+ left: `${String(rect.x * 100)}%`,
3207
+ top: `${String(rect.y * 100)}%`,
3208
+ width: `${String(rect.width * 100)}%`,
3209
+ height: `${String(rect.height * 100)}%`
3210
+ },
3211
+ children: /* @__PURE__ */ jsx("span", {
3212
+ className: "bg-doom-yellow text-doom-deep",
3213
+ children: ordinal
3214
+ })
3215
+ }, region.id);
3216
+ }
3217
+ if (source.kind !== "video" || playback.playing) return null;
3218
+ if (region.anchor.kind === "video-time-point" && Math.abs(region.anchor.timeSeconds - playback.currentTime) <= .1) return pointPin(ordinal, region.anchor.point, region.id);
3219
+ if (region.anchor.kind !== "video-time-rect" || Math.abs(region.anchor.timeSeconds - playback.currentTime) > .1) return null;
3220
+ const { rect } = region.anchor;
3221
+ return /* @__PURE__ */ jsx("div", {
3222
+ "data-author-region": ordinal,
3223
+ className: "pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10",
3224
+ style: {
3225
+ left: `${rect.x * 100}%`,
3226
+ top: `${rect.y * 100}%`,
3227
+ width: `${rect.width * 100}%`,
3228
+ height: `${rect.height * 100}%`
3229
+ },
3230
+ children: /* @__PURE__ */ jsx("span", {
3231
+ className: "bg-doom-yellow text-doom-deep",
3232
+ children: ordinal
3233
+ })
3234
+ }, region.id);
3235
+ })
3236
+ ]
3237
+ }),
3238
+ source.kind === "video" ? /* @__PURE__ */ jsx(AuthorVideoControls, {
3239
+ playback,
3240
+ ready: frameReady,
3241
+ locked: pendingCandidate,
3242
+ marking: activeTool === "mark",
3243
+ onSeek: seek,
3244
+ onToggle: () => {
3245
+ setAuthorToolMode(sessionId, "select");
3246
+ setSelection(void 0);
3247
+ if (playback.playing) video.current?.pause();
3248
+ else video.current?.play().catch((reason) => setError(String(reason)));
3249
+ },
3250
+ onAnnotate: () => {
3251
+ video.current?.pause();
3252
+ setAuthorToolMode(sessionId, "mark");
3253
+ }
3254
+ }) : null,
3255
+ error ? /* @__PURE__ */ jsx("output", {
3256
+ className: "text-base text-doom-red",
3257
+ children: error
3258
+ }) : null
3259
+ ]
3260
+ });
3261
+ }
3262
+ //#endregion
3263
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorStructuredView.tsx
3264
+ function AuthorStructuredView({ sessionId, document, displayedRegions }) {
3265
+ useEffect(() => registerAuthorGridResolver(sessionId, (cell, geometry) => {
3266
+ const { originX = 0, originY = 0, width, height } = geometry.viewport;
3267
+ const fragmentId = window.document.elementFromPoint(originX + cell.center.x * width, originY + cell.center.y * height)?.closest("[data-author-fragment]")?.dataset.authorFragment;
3268
+ const fragment = document.fragments?.find((candidate) => candidate.id === fragmentId);
3269
+ if (fragment === void 0 || fragment.readOnly === true) return void 0;
3270
+ if (document.kind === "csv" || document.kind === "xlsx") return {
3271
+ anchor: {
3272
+ kind: "cell",
3273
+ fragmentId: fragment.id,
3274
+ location: fragment.location
3275
+ },
3276
+ quote: fragment.text
3277
+ };
3278
+ const slide = Number(fragment.location.match(/(?:slide\s*|slides\/slide)(\d+)/u)?.[1]);
3279
+ if (!Number.isSafeInteger(slide)) return void 0;
3280
+ return {
3281
+ anchor: {
3282
+ kind: "slide-element",
3283
+ fragmentId: fragment.id,
3284
+ location: fragment.location,
3285
+ slide
3286
+ },
3287
+ quote: fragment.text
3288
+ };
3289
+ }), [
3290
+ document.fragments,
3291
+ document.kind,
3292
+ document.version,
3293
+ sessionId
3294
+ ]);
3295
+ const displayedByFragment = new Map(displayedRegions.flatMap(({ ordinal, region }) => region.anchor.kind === "cell" || region.anchor.kind === "slide-element" ? [[region.anchor.fragmentId, ordinal]] : []));
3296
+ const cells = document.kind === "csv" || document.kind === "xlsx";
3297
+ const groups = /* @__PURE__ */ new Map();
3298
+ for (const fragment of document.fragments ?? []) {
3299
+ const key = cells ? document.kind === "csv" ? fragment.location.split(",")[0] : fragment.location.replace(/![A-Z]+(\d+)$/, "!row $1") : fragment.location;
3300
+ const group = groups.get(key) ?? [];
3301
+ group.push(fragment);
3302
+ groups.set(key, group);
3303
+ }
3304
+ return /* @__PURE__ */ jsxs("div", {
3305
+ "data-testid": "author-structured",
3306
+ className: "min-h-0 flex-1 space-y-3 overflow-auto p-4",
3307
+ children: [!cells ? /* @__PURE__ */ jsx("p", {
3308
+ className: "text-xs text-doom-faint",
3309
+ children: "Slide text view, not a layout-faithful rendering."
3310
+ }) : null, [...groups].map(([location, fragments]) => /* @__PURE__ */ jsxs("section", {
3311
+ className: "space-y-1",
3312
+ children: [/* @__PURE__ */ jsx("h3", {
3313
+ className: "text-xs text-doom-faint",
3314
+ children: location
3315
+ }), /* @__PURE__ */ jsx("div", {
3316
+ className: cells ? "flex gap-1" : "space-y-2",
3317
+ children: fragments.map((fragment) => /* @__PURE__ */ jsxs("div", {
3318
+ "data-author-fragment": fragment.id,
3319
+ "data-author-region": displayedByFragment.get(fragment.id),
3320
+ className: `${cells ? "w-40 shrink-0" : ""} relative rounded ${displayedByFragment.has(fragment.id) ? "ring-1 ring-doom-yellow" : ""}`,
3321
+ children: [
3322
+ displayedByFragment.has(fragment.id) ? /* @__PURE__ */ jsx("span", {
3323
+ className: "absolute -left-1 -top-1 z-10 flex h-4 min-w-4 items-center justify-center rounded-full bg-doom-yellow px-1 text-2xs font-bold text-doom-deep",
3324
+ children: displayedByFragment.get(fragment.id)
3325
+ }) : null,
3326
+ /* @__PURE__ */ jsxs(Button, {
3327
+ size: "xs",
3328
+ variant: "ghost",
3329
+ onClick: (event) => {
3330
+ const bounds = event.currentTarget.getBoundingClientRect();
3331
+ const slide = Number(fragment.location.match(/(?:slide\s*|slides\/slide)(\d+)/)?.[1]);
3332
+ if (!cells && !Number.isSafeInteger(slide)) return;
3333
+ setAuthorRegionCandidate(sessionId, {
3334
+ documentPath: document.path,
3335
+ revision: document.version,
3336
+ sourceSha256: document.sourceSha256,
3337
+ quote: fragment.text,
3338
+ anchor: cells ? {
3339
+ kind: "cell",
3340
+ fragmentId: fragment.id,
3341
+ location: fragment.location
3342
+ } : {
3343
+ kind: "slide-element",
3344
+ fragmentId: fragment.id,
3345
+ location: fragment.location,
3346
+ slide
3347
+ },
3348
+ viewport: {
3349
+ width: bounds.width,
3350
+ height: bounds.height
3351
+ },
3352
+ createdAt: Date.now()
3353
+ });
3354
+ },
3355
+ children: ["mark ", fragment.location]
3356
+ }),
3357
+ /* @__PURE__ */ jsx("textarea", {
3358
+ "aria-label": fragment.location,
3359
+ value: fragment.text,
3360
+ readOnly: fragment.readOnly === true,
3361
+ className: "w-full rounded border border-doom-border bg-doom-deep p-2 text-sm text-doom-text",
3362
+ onChange: (event) => reviseAuthorFragment(sessionId, document.path, fragment.id, event.target.value)
3363
+ })
3364
+ ]
3365
+ }, fragment.id))
3366
+ })]
3367
+ }, location))]
3368
+ });
3369
+ }
3370
+ //#endregion
3371
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorTextView.tsx
3372
+ function AuthorTextView({ sessionId, document, preview, displayedRegions }) {
3373
+ const host = useRef(null);
3374
+ const editor = useRef(null);
3375
+ useEffect(() => registerAuthorGridResolver(sessionId, (cell, geometry) => {
3376
+ if (preview || editor.current === null) return void 0;
3377
+ const { originX = 0, originY = 0, width, height } = geometry.viewport;
3378
+ const range = editor.current.resolveViewportRegion({
3379
+ left: originX + cell.rect.x * width,
3380
+ top: originY + cell.rect.y * height,
3381
+ right: originX + (cell.rect.x + cell.rect.width) * width,
3382
+ bottom: originY + (cell.rect.y + cell.rect.height) * height
3383
+ });
3384
+ if (range === null || range.from === range.to) return void 0;
3385
+ return {
3386
+ anchor: {
3387
+ kind: "text-range",
3388
+ startOffset: range.from,
3389
+ endOffset: range.to,
3390
+ startLine: range.startLine,
3391
+ endLine: range.endLine
3392
+ },
3393
+ quote: range.text
3394
+ };
3395
+ }), [preview, sessionId]);
3396
+ useEffect(() => {
3397
+ editor.current?.setMarkedRanges(displayedRegions.flatMap(({ ordinal, region }) => region.anchor.kind === "text-range" ? [{
3398
+ from: region.anchor.startOffset,
3399
+ to: region.anchor.endOffset,
3400
+ label: String(ordinal)
3401
+ }] : []));
3402
+ }, [displayedRegions]);
3403
+ return /* @__PURE__ */ jsx("div", {
3404
+ ref: host,
3405
+ className: "flex min-h-0 flex-1 flex-col",
3406
+ children: preview ? /* @__PURE__ */ jsx("div", {
3407
+ "data-testid": "author-markdown",
3408
+ className: "min-h-0 flex-1 overflow-auto p-4 text-sm text-doom-text",
3409
+ children: /* @__PURE__ */ jsx(Markdown, { text: document.content ?? "" })
3410
+ }) : /* @__PURE__ */ jsx(CodeEditor, {
3411
+ value: document.content ?? "",
3412
+ path: document.path,
3413
+ "data-testid": "author-editor",
3414
+ controllerRef: editor,
3415
+ className: "min-h-0 flex-1",
3416
+ onChange: (content) => reviseAuthorDocument(sessionId, document.path, content),
3417
+ onSelect: (range) => {
3418
+ const current = authorDocument(sessionId, document.path);
3419
+ const bounds = host.current?.getBoundingClientRect();
3420
+ if (!current || !bounds || range.from === range.to) return;
3421
+ setAuthorRegionCandidate(sessionId, {
3422
+ documentPath: current.path,
3423
+ revision: current.version,
3424
+ sourceSha256: current.sourceSha256,
3425
+ quote: range.text,
3426
+ anchor: {
3427
+ kind: "text-range",
3428
+ startOffset: range.from,
3429
+ endOffset: range.to,
3430
+ startLine: range.startLine,
3431
+ endLine: range.endLine
3432
+ },
3433
+ viewport: {
3434
+ width: bounds.width,
3435
+ height: bounds.height
3436
+ },
3437
+ createdAt: Date.now()
3438
+ });
3439
+ }
3440
+ })
3441
+ });
3442
+ }
3443
+ //#endregion
3444
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorDocumentPanel.tsx
3445
+ function tabId(path) {
3446
+ let hash = 2166136261;
3447
+ for (const char of normalizeAuthorPath(path)) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619);
3448
+ return `author-file-${(hash >>> 0).toString(36)}`;
3449
+ }
3450
+ function displayedAuthorRegions(workspace) {
3451
+ const draftRegions = workspace?.regions ?? [];
3452
+ if (draftRegions.length > 0) return draftRegions.map((region, index) => ({
3453
+ ordinal: index + 1,
3454
+ region
3455
+ }));
3456
+ const activeRequest = workspace?.requests.slice().reverse().find((request) => request.status === "REQUESTED" || request.status === "CHANGING");
3457
+ return (activeRequest?.pendingRegions ?? []).map((region) => ({
3458
+ ordinal: Math.max(1, (activeRequest?.regions.findIndex((original) => original.id === region.id) ?? 0) + 1),
3459
+ region
3460
+ }));
3461
+ }
3462
+ function AuthorDocumentPanel(props) {
3463
+ if (!props.activeMinorModes?.includes("author")) {
3464
+ const fallback = props.fileTabFor(props.path);
3465
+ if (fallback !== void 0 && fallback.id !== tabId(props.path)) return /* @__PURE__ */ jsx(fallback.panel, { ...props });
3466
+ return /* @__PURE__ */ jsx("p", {
3467
+ "data-testid": "author-mode-inactive",
3468
+ className: "p-3 text-sm text-doom-dim",
3469
+ children: "No file viewer is available for this path."
3470
+ });
3471
+ }
3472
+ return /* @__PURE__ */ jsx(ActiveAuthorDocumentPanel, { ...props });
3473
+ }
3474
+ function ActiveAuthorDocumentPanel(props) {
3475
+ const { path, sessionId, statuses } = props;
3476
+ const document = useStore(authorWorkspace.store, (state) => sessionId === null ? void 0 : state.documents[authorDocumentKey(sessionId, path)]);
3477
+ const focusGeneration = useRef(void 0);
3478
+ const workspace = useStore(authorWorkspace.store, (state) => sessionId === null ? void 0 : state.sessions[sessionId]);
3479
+ const activeTool = workspace?.activeTool ?? "select";
3480
+ const [markdownPreview, setMarkdownPreview] = useState(true);
3481
+ const [status, setStatus] = useState();
3482
+ const [previewOpen, setPreviewOpen] = useState(false);
3483
+ const kind = document?.kind;
3484
+ const previewSource = document === void 0 ? void 0 : {
3485
+ path: document.path,
3486
+ kind: document.kind,
3487
+ hasUnsavedChanges: document.version !== document.savedVersion,
3488
+ revision: document.savedVersion,
3489
+ sourceSha256: document.sourceSha256
3490
+ };
3491
+ const PreviewPanel = (previewSource === void 0 ? void 0 : (props.slotData?.(authorPreviewActionSlot) ?? []).find(({ data }) => data.embeddedPanel !== void 0 && (data.supportsSource === void 0 || data.supportsSource(previewSource))))?.data.embeddedPanel;
3492
+ useEffect(() => {
3493
+ if (document !== void 0 || sessionId === null) return;
3494
+ const controller = new AbortController();
3495
+ setStatus("loading");
3496
+ loadAuthorDocument(sessionId, path, controller.signal).then((loaded) => {
3497
+ if (controller.signal.aborted) return;
3498
+ putAuthorDocument(sessionId, loaded);
3499
+ setStatus(void 0);
3500
+ }).catch((error) => {
3501
+ if (!controller.signal.aborted) setStatus(error instanceof Error ? error.message : String(error));
3502
+ });
3503
+ return () => controller.abort();
3504
+ }, [
3505
+ document,
3506
+ path,
3507
+ sessionId
3508
+ ]);
3509
+ useEffect(() => {
3510
+ if (document === void 0 || sessionId === null) return;
3511
+ const generation = focusAuthorDocument(sessionId, path, document.version, document.sourceSha256);
3512
+ focusGeneration.current = generation;
3513
+ return () => releaseAuthorDocumentFocus(sessionId, generation);
3514
+ }, [
3515
+ kind,
3516
+ document?.sourceSha256,
3517
+ path,
3518
+ sessionId
3519
+ ]);
3520
+ useEffect(() => {
3521
+ const generation = focusGeneration.current;
3522
+ if (document === void 0 || sessionId === null || generation === void 0) return;
3523
+ syncAuthorDocumentFocus(sessionId, generation, document.version, document.sourceSha256);
3524
+ }, [
3525
+ document?.sourceSha256,
3526
+ document?.version,
3527
+ sessionId
3528
+ ]);
3529
+ useEffect(() => {
3530
+ if (kind === void 0 || sessionId === null) return;
3531
+ const profiles = authorProfilesForDocument(sessionId, path, kind);
3532
+ let release;
3533
+ let cancelled = false;
3534
+ focusAuthorViewport(sessionId, profiles).then((nextRelease) => {
3535
+ if (cancelled) nextRelease();
3536
+ else release = nextRelease;
3537
+ });
3538
+ return () => {
3539
+ cancelled = true;
3540
+ release?.();
3541
+ };
3542
+ }, [
3543
+ kind,
3544
+ path,
3545
+ sessionId
3546
+ ]);
3547
+ if (document === void 0 || sessionId === null) return /* @__PURE__ */ jsx("p", {
3548
+ className: "p-4 text-sm text-doom-faint",
3549
+ children: status ?? "Loading document..."
3550
+ });
3551
+ const save = async () => {
3552
+ const savedVersion = document.version;
3553
+ const savedFragments = document.fragments;
3554
+ requestAuthorSave(sessionId, path);
3555
+ setStatus("saving");
3556
+ try {
3557
+ const sha256 = await saveAuthorDocument(sessionId, document);
3558
+ completeAuthorSave(sessionId, path, sha256, savedVersion, savedFragments);
3559
+ setStatus("saved");
3560
+ } catch (error) {
3561
+ failAuthorSave(sessionId, path, savedVersion);
3562
+ setStatus(error instanceof Error ? error.message : String(error));
3563
+ }
3564
+ };
3565
+ const gridVisible = autonomousVoiceGridVisible(statuses);
3566
+ const displayedRegions = displayedAuthorRegions(workspace);
3567
+ const displayedPreviewAnnotations = displayedRegions.flatMap(({ ordinal, region }) => {
3568
+ if (region.anchor.kind === "story-preview-rect") return [{
3569
+ ordinal,
3570
+ mode: "region",
3571
+ point: region.anchor.rect,
3572
+ rect: region.anchor.rect
3573
+ }];
3574
+ if (region.anchor.kind === "story-preview-point") return [{
3575
+ ordinal,
3576
+ mode: "point",
3577
+ point: region.anchor.point
3578
+ }];
3579
+ return [];
3580
+ });
3581
+ const textPanel = /* @__PURE__ */ jsx(AuthorTextView, {
3582
+ sessionId,
3583
+ document,
3584
+ preview: document.kind === "markdown" && markdownPreview && activeTool === "select" && !gridVisible,
3585
+ displayedRegions
3586
+ });
3587
+ const sourcePanel = document.kind === "text" || document.kind === "markdown" || document.kind === "story-preview" ? textPanel : document.structuredFormat !== void 0 ? /* @__PURE__ */ jsx(AuthorStructuredView, {
3588
+ sessionId,
3589
+ document,
3590
+ displayedRegions
3591
+ }) : /* @__PURE__ */ jsx(AuthorMediaView, {
3592
+ sessionId,
3593
+ document,
3594
+ activeTool,
3595
+ displayedRegions,
3596
+ pendingCandidate: workspace?.candidate !== void 0,
3597
+ seekRequest: workspace?.videoSeekRequest
3598
+ }, document.path);
3599
+ return /* @__PURE__ */ jsxs("section", {
3600
+ "data-testid": "author-document",
3601
+ className: "flex min-h-0 flex-1 flex-col",
3602
+ children: [
3603
+ /* @__PURE__ */ jsxs("header", {
3604
+ className: "flex items-center gap-2 border-b border-doom-border px-4 py-2",
3605
+ children: [
3606
+ /* @__PURE__ */ jsx("strong", {
3607
+ className: "min-w-0 flex-1 truncate text-base text-doom-hi",
3608
+ children: document.title ?? document.path
3609
+ }),
3610
+ PreviewPanel !== void 0 && previewSource !== void 0 ? /* @__PURE__ */ jsx(Button, {
3611
+ size: "xs",
3612
+ variant: previewOpen ? "primary" : "outline",
3613
+ "data-testid": "author-story-preview-toggle",
3614
+ "aria-pressed": previewOpen,
3615
+ "aria-label": previewOpen ? "show story source" : "show story preview",
3616
+ onClick: () => setPreviewOpen(!previewOpen),
3617
+ children: previewOpen ? "Source" : "Preview"
3618
+ }) : null,
3619
+ document.kind === "markdown" ? /* @__PURE__ */ jsx(Button, {
3620
+ size: "xs",
3621
+ variant: "ghost",
3622
+ "data-testid": "author-markdown-toggle",
3623
+ onClick: () => setMarkdownPreview(!markdownPreview),
3624
+ children: markdownPreview ? "edit" : "preview"
3625
+ }) : null,
3626
+ document.kind === "video" ? /* @__PURE__ */ jsx("span", {
3627
+ className: "text-base text-doom-dim",
3628
+ children: "Video feedback"
3629
+ }) : /* @__PURE__ */ jsx(Button, {
3630
+ className: "min-h-11 px-4 text-base",
3631
+ variant: "outline",
3632
+ "data-testid": "author-save",
3633
+ disabled: document.revisions.length === 0 || document.savingVersion !== void 0,
3634
+ onClick: () => void save(),
3635
+ children: previewOpen && previewSource?.hasUnsavedChanges ? "Save & refresh" : "Save"
3636
+ })
3637
+ ]
3638
+ }),
3639
+ status === void 0 ? null : /* @__PURE__ */ jsx("output", {
3640
+ className: "px-4 py-1 text-xs text-doom-faint",
3641
+ children: status
3642
+ }),
3643
+ /* @__PURE__ */ jsxs("div", {
3644
+ className: "relative flex min-h-0 flex-1 overflow-hidden",
3645
+ children: [previewOpen && PreviewPanel !== void 0 && previewSource !== void 0 ? /* @__PURE__ */ jsx(PreviewPanel, {
3646
+ ...props,
3647
+ source: previewSource,
3648
+ activeTool: activeTool === "crop" ? "select" : activeTool,
3649
+ displayedAnnotations: displayedPreviewAnnotations,
3650
+ pendingCandidate: workspace?.candidate !== void 0,
3651
+ onAnnotationCandidate: (candidate) => {
3652
+ setAuthorStoryPreview(sessionId, document.path, candidate.preview);
3653
+ setAuthorRegionCandidate(sessionId, {
3654
+ documentPath: document.path,
3655
+ revision: document.version,
3656
+ sourceSha256: document.sourceSha256,
3657
+ mode: candidate.mode,
3658
+ anchor: candidate.mode === "region" && candidate.rect !== void 0 ? {
3659
+ kind: "story-preview-rect",
3660
+ rect: candidate.rect,
3661
+ preview: candidate.preview
3662
+ } : {
3663
+ kind: "story-preview-point",
3664
+ point: candidate.point,
3665
+ preview: candidate.preview
3666
+ },
3667
+ viewport: candidate.preview.viewport,
3668
+ evidence: candidate.evidence,
3669
+ thumbnailUrl: candidate.thumbnailUrl,
3670
+ createdAt: Date.now()
3671
+ });
3672
+ }
3673
+ }) : sourcePanel, previewOpen && PreviewPanel !== void 0 && previewSource !== void 0 ? null : /* @__PURE__ */ jsx(AuthorGridOverlay, {
3674
+ sessionId,
3675
+ document,
3676
+ visible: gridVisible
3677
+ })]
3678
+ })
3679
+ ]
3680
+ });
3681
+ }
3682
+ function authorFileTab(path) {
3683
+ const normalized = normalizeAuthorPath(path);
3684
+ return {
3685
+ id: tabId(normalized),
3686
+ label: normalized.split("/").at(-1) ?? normalized,
3687
+ retainComposer: true,
3688
+ panel: (props) => /* @__PURE__ */ jsx(AuthorDocumentPanel, {
3689
+ ...props,
3690
+ path: normalized
3691
+ })
3692
+ };
3693
+ }
3694
+ //#endregion
3695
+ //#region src/extensions/workspaces/sessions/(frontend)/file-links/author-document.web.ts
3696
+ var author_document_web_default = defineFileLinks({
3697
+ requiredMinorMode: "author",
3698
+ subscribe(listener) {
3699
+ const subscription = authorWorkspace.store.subscribe(listener);
3700
+ return () => subscription.unsubscribe();
3701
+ },
3702
+ fingerprint(sessionId) {
3703
+ if (sessionId === null) return "";
3704
+ const prefix = `${sessionId}\n`;
3705
+ return Object.keys(authorWorkspace.store.state.documents).filter((key) => key.startsWith(prefix)).sort().join("\n");
3706
+ },
3707
+ resolve(sessionId, path) {
3708
+ if (sessionId === null) return void 0;
3709
+ const normalized = normalizeAuthorPath(path.replace(/:\d+(?::\d+)?$/, ""));
3710
+ return authorWorkspace.store.state.documents[authorDocumentKey(sessionId, normalized)] === void 0 ? void 0 : authorFileTab(normalized);
3711
+ },
3712
+ openPath(sessionId, path) {
3713
+ if (sessionId === null) return void 0;
3714
+ const normalized = normalizeAuthorPath(path);
3715
+ return normalized === "" ? void 0 : authorFileTab(normalized);
3716
+ }
3717
+ });
3718
+ //#endregion
3719
+ //#region src/extensions/workspaces/sessions/(frontend)/leader/author-toggle.web.ts
3720
+ var author_toggle_web_default = defineLeaderBinding({
3721
+ id: "author.toggle",
3722
+ path: [{
3723
+ key: "o",
3724
+ label: "other",
3725
+ detail: "optional modes and tools"
3726
+ }, {
3727
+ key: "a",
3728
+ label: "author",
3729
+ detail: "enable or disable focused document authoring"
3730
+ }],
3731
+ command: "minor author"
3732
+ });
3733
+ //#endregion
3734
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/authorAnnotationPersistence.ts
3735
+ const AUTHOR_ANNOTATION_DATABASE = "doompi-author-annotations";
3736
+ const STORE = "documents";
3737
+ const RECORD_VERSION = 1;
3738
+ function object(value) {
3739
+ return typeof value === "object" && value !== null;
3740
+ }
3741
+ function finite(value) {
3742
+ return typeof value === "number" && Number.isFinite(value);
3743
+ }
3744
+ function point(value) {
3745
+ return object(value) && finite(value.x) && finite(value.y) && value.x >= 0 && value.x <= 1 && value.y >= 0 && value.y <= 1;
3746
+ }
3747
+ function rect(value) {
3748
+ return point(value) && object(value) && finite(value.width) && finite(value.height) && value.width > 0 && value.height > 0;
3749
+ }
3750
+ function validAnchor(value) {
3751
+ if (!object(value) || typeof value.kind !== "string") return false;
3752
+ switch (value.kind) {
3753
+ case "text-range": return finite(value.startOffset) && finite(value.endOffset) && finite(value.startLine) && finite(value.endLine);
3754
+ case "cell": return typeof value.fragmentId === "string" && typeof value.location === "string";
3755
+ case "slide-element": return typeof value.fragmentId === "string" && finite(value.slide) && typeof value.location === "string";
3756
+ case "image-rect": return rect(value.rect) && finite(value.naturalWidth) && finite(value.naturalHeight);
3757
+ case "pdf-page-rect": return finite(value.page) && rect(value.rect);
3758
+ case "video-time-rect": return finite(value.timeSeconds) && rect(value.rect);
3759
+ case "story-preview-rect": return rect(value.rect) && object(value.preview);
3760
+ case "image-point": return point(value.point) && finite(value.naturalWidth) && finite(value.naturalHeight);
3761
+ case "pdf-page-point": return finite(value.page) && point(value.point);
3762
+ case "video-time-point": return finite(value.timeSeconds) && point(value.point);
3763
+ case "story-preview-point": return point(value.point) && object(value.preview);
3764
+ default: return false;
3765
+ }
3766
+ }
3767
+ function validEvidence(value) {
3768
+ return value === void 0 || typeof Blob !== "undefined" && value instanceof Blob && value.size <= 8388608;
3769
+ }
3770
+ function validCandidate(value) {
3771
+ if (!object(value)) return false;
3772
+ return typeof value.documentPath === "string" && normalizeAuthorPath(value.documentPath) !== "" && finite(value.revision) && (value.sourceSha256 === void 0 || typeof value.sourceSha256 === "string") && (value.mode === void 0 || value.mode === "region" || value.mode === "point") && validAnchor(value.anchor) && object(value.viewport) && finite(value.viewport.width) && finite(value.viewport.height) && finite(value.createdAt) && validEvidence(value.evidence);
3773
+ }
3774
+ function validAnnotation(value) {
3775
+ return validCandidate(value) && object(value) && typeof value.id === "string" && value.id !== "" && typeof value.comment === "string" && value.comment.trim() !== "" && (value.version === void 0 || Number.isInteger(value.version) && value.version >= 1);
3776
+ }
3777
+ function utf8Length(value) {
3778
+ return new TextEncoder().encode(value).byteLength;
3779
+ }
3780
+ function validateAuthorAnnotationRecord(input) {
3781
+ if (!object(input) || input.version !== RECORD_VERSION || typeof input.key !== "string") return void 0;
3782
+ if (typeof input.sessionId !== "string" || input.sessionId === "" || typeof input.documentPath !== "string") return void 0;
3783
+ const path = normalizeAuthorPath(input.documentPath);
3784
+ if (path === "" || input.key !== `${input.sessionId}\n${path}` || !object(input.collection)) return void 0;
3785
+ const collection = input.collection;
3786
+ if (!finite(collection.revision) || collection.sourceSha256 !== void 0 && typeof collection.sourceSha256 !== "string" || typeof collection.stale !== "boolean" || typeof collection.candidateText !== "string" || utf8Length(collection.candidateText) > 2048 || !Array.isArray(collection.annotations) || collection.annotations.length > 16 || !collection.annotations.every(validAnnotation) || collection.candidate !== void 0 && !validCandidate(collection.candidate) || !finite(collection.updatedAt)) return void 0;
3787
+ return input;
3788
+ }
3789
+ function durableCandidate(candidate) {
3790
+ const { thumbnailUrl: _thumbnailUrl, ...durable } = candidate;
3791
+ return durable;
3792
+ }
3793
+ function persistenceRecords() {
3794
+ const records = [];
3795
+ for (const [sessionId, session] of Object.entries(authorWorkspace.store.state.sessions)) for (const [documentPath, collection] of Object.entries(session.annotationsByDocument)) {
3796
+ if (collection.annotations.length === 0 && collection.candidate === void 0 && collection.candidateText === "") continue;
3797
+ records.push({
3798
+ key: `${sessionId}\n${documentPath}`,
3799
+ version: RECORD_VERSION,
3800
+ sessionId,
3801
+ documentPath,
3802
+ collection: {
3803
+ ...collection,
3804
+ candidate: collection.candidate && durableCandidate(collection.candidate),
3805
+ annotations: collection.annotations.map(durableCandidate)
3806
+ }
3807
+ });
3808
+ }
3809
+ return records.sort((a, b) => b.collection.updatedAt - a.collection.updatedAt).slice(0, 100);
3810
+ }
3811
+ var IndexedDbAuthorAnnotationRepository = class {
3812
+ #database;
3813
+ #writes = Promise.resolve();
3814
+ constructor(factory) {
3815
+ this.#database = new Promise((resolve, reject) => {
3816
+ const request = factory.open(AUTHOR_ANNOTATION_DATABASE, 1);
3817
+ request.onupgradeneeded = () => {
3818
+ if (!request.result.objectStoreNames.contains(STORE)) request.result.createObjectStore(STORE, { keyPath: "key" });
3819
+ };
3820
+ request.onsuccess = () => resolve(request.result);
3821
+ request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("Unable to open Author annotation storage."));
3822
+ });
3823
+ }
3824
+ async load() {
3825
+ const database = await this.#database;
3826
+ return await new Promise((resolve, reject) => {
3827
+ const request = database.transaction(STORE, "readonly").objectStore(STORE).getAll();
3828
+ request.onsuccess = () => resolve(request.result);
3829
+ request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("Unable to read Author annotations."));
3830
+ });
3831
+ }
3832
+ replace(records) {
3833
+ const write = async () => {
3834
+ const database = await this.#database;
3835
+ await new Promise((resolve, reject) => {
3836
+ const transaction = database.transaction(STORE, "readwrite");
3837
+ const store = transaction.objectStore(STORE);
3838
+ store.clear();
3839
+ records.forEach((record) => store.put(record));
3840
+ transaction.oncomplete = () => resolve();
3841
+ transaction.onerror = () => reject(transaction.error ?? /* @__PURE__ */ new Error("Unable to write Author annotations."));
3842
+ transaction.onabort = () => reject(transaction.error ?? /* @__PURE__ */ new Error("Author annotation write was aborted."));
3843
+ });
3844
+ };
3845
+ this.#writes = this.#writes.catch(() => void 0).then(write);
3846
+ return this.#writes;
3847
+ }
3848
+ async close() {
3849
+ try {
3850
+ await this.#writes;
3851
+ } finally {
3852
+ (await this.#database).close();
3853
+ }
3854
+ }
3855
+ };
3856
+ function startAuthorAnnotationPersistence(repository) {
3857
+ const factory = typeof indexedDB === "undefined" ? void 0 : indexedDB;
3858
+ if (repository === void 0 && factory === void 0) return async () => void 0;
3859
+ const storage = repository ?? new IndexedDbAuthorAnnotationRepository(factory);
3860
+ let stopped = false;
3861
+ let hydrated = false;
3862
+ let dirty = false;
3863
+ const subscription = authorWorkspace.store.subscribe(() => {
3864
+ if (!hydrated) dirty = true;
3865
+ else storage.replace(persistenceRecords()).catch(() => void 0);
3866
+ });
3867
+ storage.load().then((values) => {
3868
+ if (stopped) return;
3869
+ const records = values.map(validateAuthorAnnotationRecord).filter((record) => record !== void 0).sort((a, b) => b.collection.updatedAt - a.collection.updatedAt).slice(0, 100);
3870
+ hydrateAuthorAnnotationCollections(records);
3871
+ hydrated = true;
3872
+ if (dirty || records.length > 0) storage.replace(persistenceRecords()).catch(() => void 0);
3873
+ }).catch(() => {
3874
+ if (stopped) return;
3875
+ hydrated = true;
3876
+ if (dirty) storage.replace(persistenceRecords()).catch(() => void 0);
3877
+ });
3878
+ return async () => {
3879
+ stopped = true;
3880
+ subscription.unsubscribe();
3881
+ await storage.close();
3882
+ };
3883
+ }
3884
+ //#endregion
3885
+ //#region src/extensions/workspaces/sessions/(frontend)/lifecycle/_lib/authorRequestLifecycle.ts
3886
+ function capturePacket(content) {
3887
+ try {
3888
+ const value = JSON.parse(content);
3889
+ if (value.version !== 1 || typeof value.captureId !== "string" || typeof value.capturedAt !== "number" || typeof value.document?.path !== "string" || typeof value.document.revision !== "number" || !Array.isArray(value.regions) || value.regions.length === 0) return;
3890
+ return value;
3891
+ } catch {
3892
+ return;
3893
+ }
3894
+ }
3895
+ function recordAuthorComposerSubmission(submission) {
3896
+ for (const item of submission.contextItems) {
3897
+ if (item.source !== "author" || item.kind !== "author-capture") continue;
3898
+ const packet = capturePacket(item.metadata ?? item.content);
3899
+ if (packet === void 0 || packet.captureId !== item.id) continue;
3900
+ if (authorSessionWorkspace(submission.sessionId).requests.some((request) => request.captureId === packet.captureId)) continue;
3901
+ const regions = packet.regions.map((region) => ({
3902
+ id: region.id,
3903
+ documentPath: packet.document.path,
3904
+ revision: packet.document.revision,
3905
+ sourceSha256: packet.document.sourceSha256,
3906
+ comment: region.comment,
3907
+ version: region.version ?? 1,
3908
+ mode: region.mode,
3909
+ quote: region.quote,
3910
+ anchor: structuredClone(region.anchor),
3911
+ viewport: { ...region.viewport },
3912
+ voiceGrid: region.voiceGrid && { ...region.voiceGrid },
3913
+ createdAt: packet.capturedAt
3914
+ }));
3915
+ putAuthorRequest(submission.sessionId, {
3916
+ id: crypto.randomUUID(),
3917
+ captureId: packet.captureId,
3918
+ documentPath: packet.document.path,
3919
+ requestText: submission.message,
3920
+ regions,
3921
+ pendingRegions: regions,
3922
+ status: "REQUESTED",
3923
+ createdAt: submission.submittedAt,
3924
+ updatedAt: submission.submittedAt,
3925
+ revision: packet.document.revision,
3926
+ sourceSha256: packet.document.sourceSha256
3927
+ });
3928
+ for (const region of packet.regions) removeAuthorRegion(submission.sessionId, region.id, region.version ?? 1, packet.document.path);
3929
+ }
3930
+ }
3931
+ /** Only the host's correlated execution events may settle a submitted capture. */
3932
+ function recordAuthorCaptureStatus(event) {
3933
+ const request = authorSessionWorkspace(event.sessionId).requests.find((record) => record.captureId === event.captureId);
3934
+ if (request === void 0 || [
3935
+ "COMPLETE",
3936
+ "FAILED",
3937
+ "CANCELLED"
3938
+ ].includes(request.status)) return;
3939
+ if (event.status === "queued") return;
3940
+ updateAuthorRequest(event.sessionId, request.id, (record) => ({
3941
+ ...record,
3942
+ status: event.status === "working" ? "CHANGING" : event.status === "completed" ? "COMPLETE" : "FAILED",
3943
+ currentOperation: event.status === "working" ? "Agent is working on this request" : void 0,
3944
+ pendingRegions: event.status === "completed" ? [] : record.pendingRegions,
3945
+ error: event.status === "error" ? event.error || "Request execution failed." : void 0,
3946
+ updatedAt: Date.now()
3947
+ }));
3948
+ }
3949
+ //#endregion
3950
+ //#region src/extensions/workspaces/sessions/(frontend)/lifecycle/_lib/authorBrowserLifecycle.ts
3951
+ function startAuthorBrowserLifecycle(runtime) {
3952
+ const stopBridge = startAuthorBrowserBridge(runtime);
3953
+ const stopPersistence = startAuthorAnnotationPersistence();
3954
+ const stopSubmissions = runtime.onComposerSubmitted?.(recordAuthorComposerSubmission) ?? (() => void 0);
3955
+ const stopCaptureStatus = runtime.onCaptureStatus?.(recordAuthorCaptureStatus) ?? (() => void 0);
3956
+ return async () => {
3957
+ stopSubmissions();
3958
+ stopCaptureStatus();
3959
+ stopBridge();
3960
+ await stopPersistence().catch(() => void 0);
3961
+ };
3962
+ }
3963
+ //#endregion
3964
+ //#region src/extensions/workspaces/sessions/(frontend)/lifecycle/start.web.ts
3965
+ var start_web_default = defineWebLifecycle(startAuthorBrowserLifecycle);
3966
+ //#endregion
3967
+ //#region src/extensions/workspaces/sessions/(frontend)/mode/author/mode.web.ts
3968
+ var mode_web_default = defineMinorModeFile({
3969
+ name: "author",
3970
+ keys: "o a",
3971
+ order: 45
3972
+ });
3973
+ //#endregion
3974
+ //#region src/extensions/workspaces/sessions/(frontend)/slot/preview-action.web.ts
3975
+ /** Typed data slot for preview launchers that need Author's focused source. */
3976
+ var preview_action_web_default = defineSlotFile({
3977
+ slot: "author.preview-action",
3978
+ parse: parseAuthorPreviewAction
3979
+ });
3980
+ //#endregion
3981
+ //#region src/extensions/workspaces/sessions/(frontend)/slot/preview-provider.web.ts
3982
+ /**
3983
+ * Optional preview providers render launch controls here. The provider owns its
3984
+ * transient tab and preview lifecycle; Author owns the surrounding mode and
3985
+ * composer submission surface.
3986
+ */
3987
+ var preview_provider_web_default = defineSlotFile({});
3988
+ //#endregion
3989
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/describeAuthorToolsToolRender.ts
3990
+ function describeAuthorToolsCallSummary(_args) {
3991
+ return "";
3992
+ }
3993
+ /**
3994
+ * The body as toned lines: what the result's details say, or its text. Pure,
3995
+ * so a unit test can pin it without a DOM; the item decides how many to show.
3996
+ */
3997
+ function describeAuthorToolsResultLines(result) {
3998
+ if (result === null) return [];
3999
+ return toolResultTextLines(result.content).map((text) => ({
4000
+ text,
4001
+ tone: "dim"
4002
+ }));
4003
+ }
4004
+ //#endregion
4005
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_components/DescribeAuthorToolsToolCard.tsx
4006
+ /**
4007
+ * The describe_author_tools tool's timeline item: Describe the current Author viewport capabilities. The shell owns
4008
+ * the frame, the outcome tone, the status badge, and the expand toggle; this
4009
+ * card supplies the header summary and the body lines, and says whether it
4010
+ * hides any so the toggle appears only when it reveals something.
4011
+ */
4012
+ function DescribeAuthorToolsToolCard({ toolName, args, result, running, isError }) {
4013
+ const lines = describeAuthorToolsResultLines(result);
4014
+ const collapsed = collapseLines(lines, 8, false);
4015
+ return /* @__PURE__ */ jsx(MessageItem, {
4016
+ tone: toolTone({
4017
+ running,
4018
+ isError
4019
+ }),
4020
+ expandable: collapsed.hidden > 0,
4021
+ children: ({ expanded }) => {
4022
+ const view = expanded ? collapseLines(lines, 8, true) : collapsed;
4023
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
4024
+ title: toolName,
4025
+ children: /* @__PURE__ */ jsx("span", {
4026
+ "data-testid": `tool-call-${toolName}`,
4027
+ className: "min-w-0 flex-1 truncate text-doom-text",
4028
+ children: describeAuthorToolsCallSummary(args)
4029
+ })
4030
+ }), view.shown.length > 0 || running || isError ? /* @__PURE__ */ jsxs(MessageItemBody, {
4031
+ "data-testid": `tool-result-${toolName}`,
4032
+ className: "flex flex-col gap-1",
4033
+ children: [
4034
+ view.shown.length > 0 ? /* @__PURE__ */ jsx(MessageLines, { lines: view.shown }) : null,
4035
+ view.hidden > 0 ? /* @__PURE__ */ jsxs(MessageItemStatus, {
4036
+ expands: true,
4037
+ children: [view.hidden, " more line(s)"]
4038
+ }) : null,
4039
+ running ? /* @__PURE__ */ jsx(MessageItemStatus, {
4040
+ tone: "running",
4041
+ children: "running"
4042
+ }) : null,
4043
+ isError ? /* @__PURE__ */ jsx(MessageItemStatus, {
4044
+ tone: "error",
4045
+ children: "failed"
4046
+ }) : null
4047
+ ]
4048
+ }) : null] });
4049
+ }
4050
+ });
4051
+ }
4052
+ //#endregion
4053
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/describe-author-tools.web.tsx
4054
+ var describe_author_tools_web_default = defineToolRenderer({
4055
+ tools: ["describe_author_tools"],
4056
+ message: DescribeAuthorToolsToolCard
4057
+ });
4058
+ //#endregion
4059
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_components/OpenAuthoringFileToolCard.tsx
4060
+ function openAuthoringFileTab(path, openTransientTab, openTab) {
4061
+ const tab = authorFileTab(path);
4062
+ openTransientTab(tab);
4063
+ openTab(tab.id);
4064
+ }
4065
+ function resultPath(result) {
4066
+ if (typeof result?.details !== "object" || result.details === null || !("path" in result.details)) return void 0;
4067
+ return typeof result.details.path === "string" ? result.details.path : void 0;
4068
+ }
4069
+ function OpenAuthoringFileToolCard({ args, result, running, isError, output, openTransientTab, openTab }) {
4070
+ const path = resultPath(result);
4071
+ const unavailable = !running && !isError && result !== null && path === void 0;
4072
+ const errorLines = isError ? (output ?? "").trim().split(/\r?\n/u).filter(Boolean).slice(0, 4).map((text) => ({
4073
+ text,
4074
+ tone: "error"
4075
+ })) : [];
4076
+ const requestedPath = typeof args.path === "string" ? args.path : "document";
4077
+ return /* @__PURE__ */ jsx(MessageItem, {
4078
+ tone: toolTone({
4079
+ running,
4080
+ isError: isError || unavailable
4081
+ }),
4082
+ children: () => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
4083
+ title: "open_authoring_file",
4084
+ children: /* @__PURE__ */ jsx("span", {
4085
+ "data-testid": "tool-call-open_authoring_file",
4086
+ className: "min-w-0 flex-1 truncate text-doom-text",
4087
+ children: requestedPath
4088
+ })
4089
+ }), /* @__PURE__ */ jsxs(MessageItemBody, {
4090
+ "data-testid": "tool-result-open_authoring_file",
4091
+ className: "flex flex-col gap-1",
4092
+ children: [
4093
+ errorLines.length > 0 ? /* @__PURE__ */ jsx(MessageLines, { lines: errorLines }) : null,
4094
+ /* @__PURE__ */ jsx(MessageItemStatus, {
4095
+ tone: isError || unavailable ? "error" : running ? "running" : void 0,
4096
+ children: isError ? "failed" : running ? "validating" : unavailable ? "unavailable" : path === void 0 ? "waiting" : "ready"
4097
+ }),
4098
+ !running && !isError && path !== void 0 ? /* @__PURE__ */ jsx(Button, {
4099
+ size: "xs",
4100
+ variant: "outline",
4101
+ "data-testid": "author-open-file",
4102
+ onClick: () => openAuthoringFileTab(path, openTransientTab, openTab),
4103
+ children: "open in Author"
4104
+ }) : null
4105
+ ]
4106
+ })] })
4107
+ });
4108
+ }
4109
+ //#endregion
4110
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/open-authoring-file.web.tsx
4111
+ var open_authoring_file_web_default = defineToolRenderer({
4112
+ tools: ["open_authoring_file"],
4113
+ message: OpenAuthoringFileToolCard,
4114
+ completionTab: ({ result }) => {
4115
+ const path = resultPath(result);
4116
+ return path === void 0 ? void 0 : authorFileTab(path);
4117
+ }
4118
+ });
4119
+ //#endregion
4120
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/useAuthorToolsToolRender.ts
4121
+ function useAuthorToolsCallSummary(args) {
4122
+ return typeof args.name === "string" ? args.name : "";
4123
+ }
4124
+ /**
4125
+ * The body as toned lines: what the result's details say, or its text. Pure,
4126
+ * so a unit test can pin it without a DOM; the item decides how many to show.
4127
+ */
4128
+ function useAuthorToolsResultLines(result) {
4129
+ if (result === null) return [];
4130
+ return toolResultTextLines(result.content).map((text) => ({
4131
+ text,
4132
+ tone: "dim"
4133
+ }));
4134
+ }
4135
+ //#endregion
4136
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_components/UseAuthorToolsToolCard.tsx
4137
+ /**
4138
+ * The use_author_tools tool's timeline item: Use one capability of the current Author viewport. The shell owns
4139
+ * the frame, the outcome tone, the status badge, and the expand toggle; this
4140
+ * card supplies the header summary and the body lines, and says whether it
4141
+ * hides any so the toggle appears only when it reveals something.
4142
+ */
4143
+ function UseAuthorToolsToolCard({ toolName, args, result, running, isError }) {
4144
+ const lines = useAuthorToolsResultLines(result);
4145
+ const collapsed = collapseLines(lines, 8, false);
4146
+ return /* @__PURE__ */ jsx(MessageItem, {
4147
+ tone: toolTone({
4148
+ running,
4149
+ isError
4150
+ }),
4151
+ expandable: collapsed.hidden > 0,
4152
+ children: ({ expanded }) => {
4153
+ const view = expanded ? collapseLines(lines, 8, true) : collapsed;
4154
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
4155
+ title: toolName,
4156
+ children: /* @__PURE__ */ jsx("span", {
4157
+ "data-testid": `tool-call-${toolName}`,
4158
+ className: "min-w-0 flex-1 truncate text-doom-text",
4159
+ children: useAuthorToolsCallSummary(args)
4160
+ })
4161
+ }), view.shown.length > 0 || running || isError ? /* @__PURE__ */ jsxs(MessageItemBody, {
4162
+ "data-testid": `tool-result-${toolName}`,
4163
+ className: "flex flex-col gap-1",
4164
+ children: [
4165
+ view.shown.length > 0 ? /* @__PURE__ */ jsx(MessageLines, { lines: view.shown }) : null,
4166
+ view.hidden > 0 ? /* @__PURE__ */ jsxs(MessageItemStatus, {
4167
+ expands: true,
4168
+ children: [view.hidden, " more line(s)"]
4169
+ }) : null,
4170
+ running ? /* @__PURE__ */ jsx(MessageItemStatus, {
4171
+ tone: "running",
4172
+ children: "running"
4173
+ }) : null,
4174
+ isError ? /* @__PURE__ */ jsx(MessageItemStatus, {
4175
+ tone: "error",
4176
+ children: "failed"
4177
+ }) : null
4178
+ ]
4179
+ }) : null] });
4180
+ }
4181
+ });
4182
+ }
4183
+ //#endregion
4184
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/use-author-tools.web.tsx
4185
+ var use_author_tools_web_default = defineToolRenderer({
4186
+ tools: ["use_author_tools"],
4187
+ message: UseAuthorToolsToolCard
4188
+ });
4189
+ //#endregion
4190
+ //#region generated/web.ts
4191
+ const via = (identity, value) => ({
4192
+ ...identity,
4193
+ ...value
4194
+ });
4195
+ const webPlugin = defineWebPlugin({
4196
+ id: "author",
4197
+ session: {
4198
+ get channels() {
4199
+ return [via({ channel: "author_webmcp" }, author_webmcp_web_default)];
4200
+ },
4201
+ get dockFaces() {
4202
+ return [via({ id: "authoring" }, authoring_web_default)];
4203
+ },
4204
+ get fileLinks() {
4205
+ return author_document_web_default;
4206
+ },
4207
+ get leaderBindings() {
4208
+ return [via({ id: "author.author-toggle" }, author_toggle_web_default)];
4209
+ },
4210
+ get minorModes() {
4211
+ return [via({ name: "author" }, mode_web_default)];
4212
+ },
4213
+ get slots() {
4214
+ return [via({ slot: "author.preview-action" }, preview_action_web_default), via({ slot: "author.preview-provider" }, preview_provider_web_default)];
4215
+ },
4216
+ get start() {
4217
+ return start_web_default;
4218
+ },
4219
+ get toolRenderers() {
4220
+ return [
4221
+ via({ tools: ["describe_author_tools"] }, describe_author_tools_web_default),
4222
+ via({ tools: ["open_authoring_file"] }, open_authoring_file_web_default),
4223
+ via({ tools: ["use_author_tools"] }, use_author_tools_web_default)
4224
+ ];
4225
+ }
4226
+ }
4227
+ });
4228
+ //#endregion
4229
+ export { webPlugin };
4230
+
4231
+ //# sourceMappingURL=web.mjs.map