papilot 0.8.7 → 0.8.9

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 (358) hide show
  1. package/dist/assets/78-EUC-H-Uf9aYsyl.bcmap +0 -0
  2. package/dist/assets/78-EUC-V-ChJxef36.bcmap +0 -0
  3. package/dist/assets/78-H-CSvm1opZ.bcmap +0 -0
  4. package/dist/assets/78-RKSJ-H-34XPL1-s.bcmap +0 -0
  5. package/dist/assets/78-RKSJ-V-CyaguwXf.bcmap +0 -0
  6. package/dist/assets/78-V-CJGcNX1t.bcmap +0 -0
  7. package/dist/assets/78ms-RKSJ-H-BuwLY695.bcmap +0 -0
  8. package/dist/assets/78ms-RKSJ-V-4hl06FMz.bcmap +0 -0
  9. package/dist/assets/83pv-RKSJ-H-DNQtuaxs.bcmap +0 -0
  10. package/dist/assets/90ms-RKSJ-H-Bhsfh4Ys.bcmap +0 -0
  11. package/dist/assets/90ms-RKSJ-V-BbstlayK.bcmap +0 -0
  12. package/dist/assets/90msp-RKSJ-H-GTsoumJQ.bcmap +0 -0
  13. package/dist/assets/90msp-RKSJ-V-BWep_0YK.bcmap +0 -0
  14. package/dist/assets/90pv-RKSJ-H-DOcWKG7K.bcmap +0 -0
  15. package/dist/assets/90pv-RKSJ-V-BuoQaCHG.bcmap +0 -0
  16. package/dist/assets/Add-H-1H-FQZVX.bcmap +0 -0
  17. package/dist/assets/Add-RKSJ-H-BpPRzomz.bcmap +0 -0
  18. package/dist/assets/Add-RKSJ-V-C7YDsPzV.bcmap +0 -0
  19. package/dist/assets/Add-V-CzFvKnCs.bcmap +0 -0
  20. package/dist/assets/Adobe-CNS1-0-CfVBOElR.bcmap +0 -0
  21. package/dist/assets/Adobe-CNS1-1-Cs9TI-Yv.bcmap +0 -0
  22. package/dist/assets/Adobe-CNS1-2-D3SWzMKj.bcmap +0 -0
  23. package/dist/assets/Adobe-CNS1-3-BNxX-hNa.bcmap +0 -0
  24. package/dist/assets/Adobe-CNS1-4-uApBOf1n.bcmap +0 -0
  25. package/dist/assets/Adobe-CNS1-5-BWUfkzOP.bcmap +0 -0
  26. package/dist/assets/Adobe-CNS1-6-DfjBny4_.bcmap +0 -0
  27. package/dist/assets/Adobe-CNS1-UCS2-fO900upv.bcmap +0 -0
  28. package/dist/assets/Adobe-GB1-0-DCLN2jmY.bcmap +0 -0
  29. package/dist/assets/Adobe-GB1-1-C_oGAEc2.bcmap +0 -0
  30. package/dist/assets/Adobe-GB1-2-BT5g0uLo.bcmap +0 -0
  31. package/dist/assets/Adobe-GB1-3-BWe_tZZF.bcmap +0 -0
  32. package/dist/assets/Adobe-GB1-4-CYF1KDey.bcmap +0 -0
  33. package/dist/assets/Adobe-GB1-5-B89PBerI.bcmap +0 -0
  34. package/dist/assets/Adobe-GB1-UCS2-BA083yIo.bcmap +0 -0
  35. package/dist/assets/Adobe-Japan1-0-7ueYY-cc.bcmap +0 -0
  36. package/dist/assets/Adobe-Japan1-1-DymoVrld.bcmap +0 -0
  37. package/dist/assets/Adobe-Japan1-2-WYxwCoVU.bcmap +0 -0
  38. package/dist/assets/Adobe-Japan1-3-DU7EG9_k.bcmap +0 -0
  39. package/dist/assets/Adobe-Japan1-4-CPqEnWe7.bcmap +0 -0
  40. package/dist/assets/Adobe-Japan1-5-CyFviWdc.bcmap +0 -0
  41. package/dist/assets/Adobe-Japan1-6-8p9YIft8.bcmap +0 -0
  42. package/dist/assets/Adobe-Japan1-UCS2-BL-4M2vS.bcmap +0 -0
  43. package/dist/assets/Adobe-Korea1-0-A0Ew6GJu.bcmap +0 -0
  44. package/dist/assets/Adobe-Korea1-1-DFSgDZ4w.bcmap +0 -0
  45. package/dist/assets/Adobe-Korea1-2-LZ89XEQ6.bcmap +0 -0
  46. package/dist/assets/Adobe-Korea1-UCS2-BEQZuOdo.bcmap +0 -0
  47. package/dist/assets/B5-H-Cw4_8on0.bcmap +0 -0
  48. package/dist/assets/B5-V-3ShXAXP7.bcmap +0 -0
  49. package/dist/assets/B5pc-H-BLVOWhvh.bcmap +0 -0
  50. package/dist/assets/B5pc-V-1X0fY8mT.bcmap +0 -0
  51. package/dist/assets/CNS-EUC-H-C1HifF4e.bcmap +0 -0
  52. package/dist/assets/CNS-EUC-V-DYWNmJNX.bcmap +0 -0
  53. package/dist/assets/CNS1-H-DAbVz1sI.bcmap +0 -0
  54. package/dist/assets/CNS1-V-BUvSsmpl.bcmap +0 -0
  55. package/dist/assets/CNS2-H-COHfjmw4.bcmap +0 -0
  56. package/dist/assets/CNS2-V-DgHqi_gL.bcmap +3 -0
  57. package/dist/assets/ETHK-B5-H-1MLb5v9x.bcmap +0 -0
  58. package/dist/assets/ETHK-B5-V-DmzBBgI2.bcmap +0 -0
  59. package/dist/assets/ETen-B5-H-BUa4x82H.bcmap +0 -0
  60. package/dist/assets/ETen-B5-V-CyIout1L.bcmap +0 -0
  61. package/dist/assets/ETenms-B5-H-DbS1LlRr.bcmap +3 -0
  62. package/dist/assets/ETenms-B5-V-DsL3AkeM.bcmap +0 -0
  63. package/dist/assets/EUC-H-D0J4HUB1.bcmap +0 -0
  64. package/dist/assets/EUC-V-BQ9CW2K_.bcmap +0 -0
  65. package/dist/assets/Ext-H-BSOXjlXj.bcmap +0 -0
  66. package/dist/assets/Ext-RKSJ-H-DGTwwnmY.bcmap +0 -0
  67. package/dist/assets/Ext-RKSJ-V-gySMsgy2.bcmap +0 -0
  68. package/dist/assets/Ext-V-ByyLJMVu.bcmap +0 -0
  69. package/dist/assets/GB-EUC-H-OgfW-rVY.bcmap +0 -0
  70. package/dist/assets/GB-EUC-V-DUJE8ZfE.bcmap +0 -0
  71. package/dist/assets/GB-H-CjAQlF1j.bcmap +4 -0
  72. package/dist/assets/GB-V-DQcXdwec.bcmap +0 -0
  73. package/dist/assets/GBK-EUC-H-CAORqCkf.bcmap +0 -0
  74. package/dist/assets/GBK-EUC-V-bmo5E_xa.bcmap +0 -0
  75. package/dist/assets/GBK2K-H-Cv55KPk8.bcmap +0 -0
  76. package/dist/assets/GBK2K-V-VEqjiYVJ.bcmap +0 -0
  77. package/dist/assets/GBKp-EUC-H-CElrnV5g.bcmap +0 -0
  78. package/dist/assets/GBKp-EUC-V-FKuJrjD8.bcmap +0 -0
  79. package/dist/assets/GBT-EUC-H-BTsQIL1M.bcmap +0 -0
  80. package/dist/assets/GBT-EUC-V-CCaONgYB.bcmap +0 -0
  81. package/dist/assets/GBT-H-DL9h-WkS.bcmap +0 -0
  82. package/dist/assets/GBT-V-DVsZ47Yj.bcmap +0 -0
  83. package/dist/assets/GBTpc-EUC-H-DnIh5WJ3.bcmap +0 -0
  84. package/dist/assets/GBTpc-EUC-V-D_dpKF5g.bcmap +0 -0
  85. package/dist/assets/GBpc-EUC-H-4mXfmDQd.bcmap +0 -0
  86. package/dist/assets/GBpc-EUC-V-BNBtOgzw.bcmap +0 -0
  87. package/dist/assets/H-BJYV6_el.bcmap +0 -0
  88. package/dist/assets/HKdla-B5-H-Bg4tZIUs.bcmap +0 -0
  89. package/dist/assets/HKdla-B5-V-CHovDFKh.bcmap +0 -0
  90. package/dist/assets/HKdlb-B5-H-Bf7o2mkD.bcmap +0 -0
  91. package/dist/assets/HKdlb-B5-V-CDfiFKye.bcmap +0 -0
  92. package/dist/assets/HKgccs-B5-H-BSg_4Hb2.bcmap +0 -0
  93. package/dist/assets/HKgccs-B5-V-Bb0FD7uo.bcmap +0 -0
  94. package/dist/assets/HKm314-B5-H-CUeI2x46.bcmap +0 -0
  95. package/dist/assets/HKm314-B5-V-BrK5Z_J-.bcmap +0 -0
  96. package/dist/assets/HKm471-B5-H-B_3IWC_G.bcmap +0 -0
  97. package/dist/assets/HKm471-B5-V-B7Ks-vkf.bcmap +0 -0
  98. package/dist/assets/HKscs-B5-H-CXbBbq6g.bcmap +0 -0
  99. package/dist/assets/HKscs-B5-V-CrljkNDo.bcmap +0 -0
  100. package/dist/assets/Hankaku-SMDjWZXJ.bcmap +0 -0
  101. package/dist/assets/Hiragana-Dy6yEHKG.bcmap +0 -0
  102. package/dist/assets/KSC-EUC-H-CYoqfsaA.bcmap +0 -0
  103. package/dist/assets/KSC-EUC-V-C8-Y2jkl.bcmap +0 -0
  104. package/dist/assets/KSC-H-CUtFgdXl.bcmap +0 -0
  105. package/dist/assets/KSC-Johab-H-CGtV3MPT.bcmap +0 -0
  106. package/dist/assets/KSC-Johab-V-CWzqnmVV.bcmap +0 -0
  107. package/dist/assets/KSC-V-CDXb0xyy.bcmap +0 -0
  108. package/dist/assets/KSCms-UHC-H-i6eA63f9.bcmap +0 -0
  109. package/dist/assets/KSCms-UHC-HW-H-lQjjyo98.bcmap +0 -0
  110. package/dist/assets/KSCms-UHC-HW-V-DKemUgQi.bcmap +0 -0
  111. package/dist/assets/KSCms-UHC-V-DaEePsjg.bcmap +0 -0
  112. package/dist/assets/KSCpc-EUC-H-BxNLD7US.bcmap +0 -0
  113. package/dist/assets/KSCpc-EUC-V-Bd6pD5ID.bcmap +0 -0
  114. package/dist/assets/Katakana-DueN_6wN.bcmap +0 -0
  115. package/dist/assets/NWP-H-BSh0nlh5.bcmap +0 -0
  116. package/dist/assets/NWP-V-DvbaGBQ0.bcmap +0 -0
  117. package/dist/assets/RKSJ-H-CotmKIAY.bcmap +0 -0
  118. package/dist/assets/RKSJ-V-CVQ5ujKs.bcmap +0 -0
  119. package/dist/assets/Roman-CGyUGrKi.bcmap +0 -0
  120. package/dist/assets/UniCNS-UCS2-H-DWzv8UhH.bcmap +0 -0
  121. package/dist/assets/UniCNS-UCS2-V-CbsnO1ia.bcmap +0 -0
  122. package/dist/assets/UniCNS-UTF16-H-Bw0JKgCq.bcmap +0 -0
  123. package/dist/assets/UniCNS-UTF16-V-DoPO_42D.bcmap +0 -0
  124. package/dist/assets/UniCNS-UTF32-H-IE_GrH2i.bcmap +0 -0
  125. package/dist/assets/UniCNS-UTF32-V-DpuHKqNE.bcmap +0 -0
  126. package/dist/assets/UniCNS-UTF8-H-BbLcFh6Q.bcmap +0 -0
  127. package/dist/assets/UniCNS-UTF8-V-BD4cmnNX.bcmap +0 -0
  128. package/dist/assets/UniGB-UCS2-H-D7xzRvZN.bcmap +0 -0
  129. package/dist/assets/UniGB-UCS2-V-COOBrDBb.bcmap +0 -0
  130. package/dist/assets/UniGB-UTF16-H-DKjbEwc0.bcmap +0 -0
  131. package/dist/assets/UniGB-UTF16-V-E1RoE6KW.bcmap +0 -0
  132. package/dist/assets/UniGB-UTF32-H-Behfouj5.bcmap +0 -0
  133. package/dist/assets/UniGB-UTF32-V-BFm4myiz.bcmap +0 -0
  134. package/dist/assets/UniGB-UTF8-H-EROft1ro.bcmap +0 -0
  135. package/dist/assets/UniGB-UTF8-V-C8SGUjFI.bcmap +0 -0
  136. package/dist/assets/UniJIS-UCS2-H-2DbZ0MaQ.bcmap +0 -0
  137. package/dist/assets/UniJIS-UCS2-HW-H-BfJ3t__Z.bcmap +0 -0
  138. package/dist/assets/UniJIS-UCS2-HW-V-BZyQaeYZ.bcmap +0 -0
  139. package/dist/assets/UniJIS-UCS2-V-Cr3xs5m4.bcmap +0 -0
  140. package/dist/assets/UniJIS-UTF16-H-BlDDPdCK.bcmap +0 -0
  141. package/dist/assets/UniJIS-UTF16-V-DfK_Aqy1.bcmap +0 -0
  142. package/dist/assets/UniJIS-UTF32-H-BIQPWkjv.bcmap +0 -0
  143. package/dist/assets/UniJIS-UTF32-V-0rh54ED3.bcmap +0 -0
  144. package/dist/assets/UniJIS-UTF8-H-CGusholx.bcmap +0 -0
  145. package/dist/assets/UniJIS-UTF8-V-BGWFHinB.bcmap +0 -0
  146. package/dist/assets/UniJIS2004-UTF16-H-KXixH-HI.bcmap +0 -0
  147. package/dist/assets/UniJIS2004-UTF16-V-BSHgoaRy.bcmap +0 -0
  148. package/dist/assets/UniJIS2004-UTF32-H-CXrD-9YE.bcmap +0 -0
  149. package/dist/assets/UniJIS2004-UTF32-V-CH3k_ouF.bcmap +0 -0
  150. package/dist/assets/UniJIS2004-UTF8-H-BkOctZjn.bcmap +0 -0
  151. package/dist/assets/UniJIS2004-UTF8-V-D8AJhywu.bcmap +0 -0
  152. package/dist/assets/UniJISPro-UCS2-HW-V-DLNkNO1x.bcmap +0 -0
  153. package/dist/assets/UniJISPro-UCS2-V-BpvX7JBA.bcmap +0 -0
  154. package/dist/assets/UniJISPro-UTF8-V-Db5K6XXE.bcmap +0 -0
  155. package/dist/assets/UniJISX0213-UTF32-H-BavAhZjb.bcmap +0 -0
  156. package/dist/assets/UniJISX0213-UTF32-V-Cz7JBDr9.bcmap +0 -0
  157. package/dist/assets/UniJISX02132004-UTF32-H-DAH-AgUq.bcmap +0 -0
  158. package/dist/assets/UniJISX02132004-UTF32-V-BqqDIAe5.bcmap +0 -0
  159. package/dist/assets/UniKS-UCS2-H-YeUc7k1L.bcmap +0 -0
  160. package/dist/assets/UniKS-UCS2-V-ahlksFWQ.bcmap +0 -0
  161. package/dist/assets/UniKS-UTF16-H-B0dvB-Td.bcmap +0 -0
  162. package/dist/assets/UniKS-UTF16-V-EgvK-6IG.bcmap +0 -0
  163. package/dist/assets/UniKS-UTF32-H-olR_LtmR.bcmap +0 -0
  164. package/dist/assets/UniKS-UTF32-V-CnOTbPsn.bcmap +0 -0
  165. package/dist/assets/UniKS-UTF8-H-CbpebD-y.bcmap +0 -0
  166. package/dist/assets/UniKS-UTF8-V-BIFFPWWm.bcmap +0 -0
  167. package/dist/assets/V-BJkwLeFB.bcmap +0 -0
  168. package/dist/assets/WP-Symbol-C3zzKJnO.bcmap +0 -0
  169. package/dist/assets/index-5M0Hwxth.css +1 -0
  170. package/dist/assets/index-n1pYuKIY.js +271 -0
  171. package/dist/assets/{math-CBzEH7Y2.js → math-yn5UTwU4.js} +1 -1
  172. package/dist/assets/pdf-view-hEwEpB78.js +598 -0
  173. package/dist/index.html +2 -2
  174. package/engine/dist/web/assets/78-EUC-H-Uf9aYsyl.bcmap +0 -0
  175. package/engine/dist/web/assets/78-EUC-V-ChJxef36.bcmap +0 -0
  176. package/engine/dist/web/assets/78-H-CSvm1opZ.bcmap +0 -0
  177. package/engine/dist/web/assets/78-RKSJ-H-34XPL1-s.bcmap +0 -0
  178. package/engine/dist/web/assets/78-RKSJ-V-CyaguwXf.bcmap +0 -0
  179. package/engine/dist/web/assets/78-V-CJGcNX1t.bcmap +0 -0
  180. package/engine/dist/web/assets/78ms-RKSJ-H-BuwLY695.bcmap +0 -0
  181. package/engine/dist/web/assets/78ms-RKSJ-V-4hl06FMz.bcmap +0 -0
  182. package/engine/dist/web/assets/83pv-RKSJ-H-DNQtuaxs.bcmap +0 -0
  183. package/engine/dist/web/assets/90ms-RKSJ-H-Bhsfh4Ys.bcmap +0 -0
  184. package/engine/dist/web/assets/90ms-RKSJ-V-BbstlayK.bcmap +0 -0
  185. package/engine/dist/web/assets/90msp-RKSJ-H-GTsoumJQ.bcmap +0 -0
  186. package/engine/dist/web/assets/90msp-RKSJ-V-BWep_0YK.bcmap +0 -0
  187. package/engine/dist/web/assets/90pv-RKSJ-H-DOcWKG7K.bcmap +0 -0
  188. package/engine/dist/web/assets/90pv-RKSJ-V-BuoQaCHG.bcmap +0 -0
  189. package/engine/dist/web/assets/Add-H-1H-FQZVX.bcmap +0 -0
  190. package/engine/dist/web/assets/Add-RKSJ-H-BpPRzomz.bcmap +0 -0
  191. package/engine/dist/web/assets/Add-RKSJ-V-C7YDsPzV.bcmap +0 -0
  192. package/engine/dist/web/assets/Add-V-CzFvKnCs.bcmap +0 -0
  193. package/engine/dist/web/assets/Adobe-CNS1-0-CfVBOElR.bcmap +0 -0
  194. package/engine/dist/web/assets/Adobe-CNS1-1-Cs9TI-Yv.bcmap +0 -0
  195. package/engine/dist/web/assets/Adobe-CNS1-2-D3SWzMKj.bcmap +0 -0
  196. package/engine/dist/web/assets/Adobe-CNS1-3-BNxX-hNa.bcmap +0 -0
  197. package/engine/dist/web/assets/Adobe-CNS1-4-uApBOf1n.bcmap +0 -0
  198. package/engine/dist/web/assets/Adobe-CNS1-5-BWUfkzOP.bcmap +0 -0
  199. package/engine/dist/web/assets/Adobe-CNS1-6-DfjBny4_.bcmap +0 -0
  200. package/engine/dist/web/assets/Adobe-CNS1-UCS2-fO900upv.bcmap +0 -0
  201. package/engine/dist/web/assets/Adobe-GB1-0-DCLN2jmY.bcmap +0 -0
  202. package/engine/dist/web/assets/Adobe-GB1-1-C_oGAEc2.bcmap +0 -0
  203. package/engine/dist/web/assets/Adobe-GB1-2-BT5g0uLo.bcmap +0 -0
  204. package/engine/dist/web/assets/Adobe-GB1-3-BWe_tZZF.bcmap +0 -0
  205. package/engine/dist/web/assets/Adobe-GB1-4-CYF1KDey.bcmap +0 -0
  206. package/engine/dist/web/assets/Adobe-GB1-5-B89PBerI.bcmap +0 -0
  207. package/engine/dist/web/assets/Adobe-GB1-UCS2-BA083yIo.bcmap +0 -0
  208. package/engine/dist/web/assets/Adobe-Japan1-0-7ueYY-cc.bcmap +0 -0
  209. package/engine/dist/web/assets/Adobe-Japan1-1-DymoVrld.bcmap +0 -0
  210. package/engine/dist/web/assets/Adobe-Japan1-2-WYxwCoVU.bcmap +0 -0
  211. package/engine/dist/web/assets/Adobe-Japan1-3-DU7EG9_k.bcmap +0 -0
  212. package/engine/dist/web/assets/Adobe-Japan1-4-CPqEnWe7.bcmap +0 -0
  213. package/engine/dist/web/assets/Adobe-Japan1-5-CyFviWdc.bcmap +0 -0
  214. package/engine/dist/web/assets/Adobe-Japan1-6-8p9YIft8.bcmap +0 -0
  215. package/engine/dist/web/assets/Adobe-Japan1-UCS2-BL-4M2vS.bcmap +0 -0
  216. package/engine/dist/web/assets/Adobe-Korea1-0-A0Ew6GJu.bcmap +0 -0
  217. package/engine/dist/web/assets/Adobe-Korea1-1-DFSgDZ4w.bcmap +0 -0
  218. package/engine/dist/web/assets/Adobe-Korea1-2-LZ89XEQ6.bcmap +0 -0
  219. package/engine/dist/web/assets/Adobe-Korea1-UCS2-BEQZuOdo.bcmap +0 -0
  220. package/engine/dist/web/assets/B5-H-Cw4_8on0.bcmap +0 -0
  221. package/engine/dist/web/assets/B5-V-3ShXAXP7.bcmap +0 -0
  222. package/engine/dist/web/assets/B5pc-H-BLVOWhvh.bcmap +0 -0
  223. package/engine/dist/web/assets/B5pc-V-1X0fY8mT.bcmap +0 -0
  224. package/engine/dist/web/assets/CNS-EUC-H-C1HifF4e.bcmap +0 -0
  225. package/engine/dist/web/assets/CNS-EUC-V-DYWNmJNX.bcmap +0 -0
  226. package/engine/dist/web/assets/CNS1-H-DAbVz1sI.bcmap +0 -0
  227. package/engine/dist/web/assets/CNS1-V-BUvSsmpl.bcmap +0 -0
  228. package/engine/dist/web/assets/CNS2-H-COHfjmw4.bcmap +0 -0
  229. package/engine/dist/web/assets/CNS2-V-DgHqi_gL.bcmap +3 -0
  230. package/engine/dist/web/assets/ETHK-B5-H-1MLb5v9x.bcmap +0 -0
  231. package/engine/dist/web/assets/ETHK-B5-V-DmzBBgI2.bcmap +0 -0
  232. package/engine/dist/web/assets/ETen-B5-H-BUa4x82H.bcmap +0 -0
  233. package/engine/dist/web/assets/ETen-B5-V-CyIout1L.bcmap +0 -0
  234. package/engine/dist/web/assets/ETenms-B5-H-DbS1LlRr.bcmap +3 -0
  235. package/engine/dist/web/assets/ETenms-B5-V-DsL3AkeM.bcmap +0 -0
  236. package/engine/dist/web/assets/EUC-H-D0J4HUB1.bcmap +0 -0
  237. package/engine/dist/web/assets/EUC-V-BQ9CW2K_.bcmap +0 -0
  238. package/engine/dist/web/assets/Ext-H-BSOXjlXj.bcmap +0 -0
  239. package/engine/dist/web/assets/Ext-RKSJ-H-DGTwwnmY.bcmap +0 -0
  240. package/engine/dist/web/assets/Ext-RKSJ-V-gySMsgy2.bcmap +0 -0
  241. package/engine/dist/web/assets/Ext-V-ByyLJMVu.bcmap +0 -0
  242. package/engine/dist/web/assets/FullPaper-zL_YjQvI.js +54 -0
  243. package/engine/dist/web/assets/GB-EUC-H-OgfW-rVY.bcmap +0 -0
  244. package/engine/dist/web/assets/GB-EUC-V-DUJE8ZfE.bcmap +0 -0
  245. package/engine/dist/web/assets/GB-H-CjAQlF1j.bcmap +4 -0
  246. package/engine/dist/web/assets/GB-V-DQcXdwec.bcmap +0 -0
  247. package/engine/dist/web/assets/GBK-EUC-H-CAORqCkf.bcmap +0 -0
  248. package/engine/dist/web/assets/GBK-EUC-V-bmo5E_xa.bcmap +0 -0
  249. package/engine/dist/web/assets/GBK2K-H-Cv55KPk8.bcmap +0 -0
  250. package/engine/dist/web/assets/GBK2K-V-VEqjiYVJ.bcmap +0 -0
  251. package/engine/dist/web/assets/GBKp-EUC-H-CElrnV5g.bcmap +0 -0
  252. package/engine/dist/web/assets/GBKp-EUC-V-FKuJrjD8.bcmap +0 -0
  253. package/engine/dist/web/assets/GBT-EUC-H-BTsQIL1M.bcmap +0 -0
  254. package/engine/dist/web/assets/GBT-EUC-V-CCaONgYB.bcmap +0 -0
  255. package/engine/dist/web/assets/GBT-H-DL9h-WkS.bcmap +0 -0
  256. package/engine/dist/web/assets/GBT-V-DVsZ47Yj.bcmap +0 -0
  257. package/engine/dist/web/assets/GBTpc-EUC-H-DnIh5WJ3.bcmap +0 -0
  258. package/engine/dist/web/assets/GBTpc-EUC-V-D_dpKF5g.bcmap +0 -0
  259. package/engine/dist/web/assets/GBpc-EUC-H-4mXfmDQd.bcmap +0 -0
  260. package/engine/dist/web/assets/GBpc-EUC-V-BNBtOgzw.bcmap +0 -0
  261. package/engine/dist/web/assets/H-BJYV6_el.bcmap +0 -0
  262. package/engine/dist/web/assets/HKdla-B5-H-Bg4tZIUs.bcmap +0 -0
  263. package/engine/dist/web/assets/HKdla-B5-V-CHovDFKh.bcmap +0 -0
  264. package/engine/dist/web/assets/HKdlb-B5-H-Bf7o2mkD.bcmap +0 -0
  265. package/engine/dist/web/assets/HKdlb-B5-V-CDfiFKye.bcmap +0 -0
  266. package/engine/dist/web/assets/HKgccs-B5-H-BSg_4Hb2.bcmap +0 -0
  267. package/engine/dist/web/assets/HKgccs-B5-V-Bb0FD7uo.bcmap +0 -0
  268. package/engine/dist/web/assets/HKm314-B5-H-CUeI2x46.bcmap +0 -0
  269. package/engine/dist/web/assets/HKm314-B5-V-BrK5Z_J-.bcmap +0 -0
  270. package/engine/dist/web/assets/HKm471-B5-H-B_3IWC_G.bcmap +0 -0
  271. package/engine/dist/web/assets/HKm471-B5-V-B7Ks-vkf.bcmap +0 -0
  272. package/engine/dist/web/assets/HKscs-B5-H-CXbBbq6g.bcmap +0 -0
  273. package/engine/dist/web/assets/HKscs-B5-V-CrljkNDo.bcmap +0 -0
  274. package/engine/dist/web/assets/Hankaku-SMDjWZXJ.bcmap +0 -0
  275. package/engine/dist/web/assets/Hiragana-Dy6yEHKG.bcmap +0 -0
  276. package/engine/dist/web/assets/KSC-EUC-H-CYoqfsaA.bcmap +0 -0
  277. package/engine/dist/web/assets/KSC-EUC-V-C8-Y2jkl.bcmap +0 -0
  278. package/engine/dist/web/assets/KSC-H-CUtFgdXl.bcmap +0 -0
  279. package/engine/dist/web/assets/KSC-Johab-H-CGtV3MPT.bcmap +0 -0
  280. package/engine/dist/web/assets/KSC-Johab-V-CWzqnmVV.bcmap +0 -0
  281. package/engine/dist/web/assets/KSC-V-CDXb0xyy.bcmap +0 -0
  282. package/engine/dist/web/assets/KSCms-UHC-H-i6eA63f9.bcmap +0 -0
  283. package/engine/dist/web/assets/KSCms-UHC-HW-H-lQjjyo98.bcmap +0 -0
  284. package/engine/dist/web/assets/KSCms-UHC-HW-V-DKemUgQi.bcmap +0 -0
  285. package/engine/dist/web/assets/KSCms-UHC-V-DaEePsjg.bcmap +0 -0
  286. package/engine/dist/web/assets/KSCpc-EUC-H-BxNLD7US.bcmap +0 -0
  287. package/engine/dist/web/assets/KSCpc-EUC-V-Bd6pD5ID.bcmap +0 -0
  288. package/engine/dist/web/assets/Katakana-DueN_6wN.bcmap +0 -0
  289. package/engine/dist/web/assets/NWP-H-BSh0nlh5.bcmap +0 -0
  290. package/engine/dist/web/assets/NWP-V-DvbaGBQ0.bcmap +0 -0
  291. package/engine/dist/web/assets/RKSJ-H-CotmKIAY.bcmap +0 -0
  292. package/engine/dist/web/assets/RKSJ-V-CVQ5ujKs.bcmap +0 -0
  293. package/engine/dist/web/assets/Roman-CGyUGrKi.bcmap +0 -0
  294. package/engine/dist/web/assets/UniCNS-UCS2-H-DWzv8UhH.bcmap +0 -0
  295. package/engine/dist/web/assets/UniCNS-UCS2-V-CbsnO1ia.bcmap +0 -0
  296. package/engine/dist/web/assets/UniCNS-UTF16-H-Bw0JKgCq.bcmap +0 -0
  297. package/engine/dist/web/assets/UniCNS-UTF16-V-DoPO_42D.bcmap +0 -0
  298. package/engine/dist/web/assets/UniCNS-UTF32-H-IE_GrH2i.bcmap +0 -0
  299. package/engine/dist/web/assets/UniCNS-UTF32-V-DpuHKqNE.bcmap +0 -0
  300. package/engine/dist/web/assets/UniCNS-UTF8-H-BbLcFh6Q.bcmap +0 -0
  301. package/engine/dist/web/assets/UniCNS-UTF8-V-BD4cmnNX.bcmap +0 -0
  302. package/engine/dist/web/assets/UniGB-UCS2-H-D7xzRvZN.bcmap +0 -0
  303. package/engine/dist/web/assets/UniGB-UCS2-V-COOBrDBb.bcmap +0 -0
  304. package/engine/dist/web/assets/UniGB-UTF16-H-DKjbEwc0.bcmap +0 -0
  305. package/engine/dist/web/assets/UniGB-UTF16-V-E1RoE6KW.bcmap +0 -0
  306. package/engine/dist/web/assets/UniGB-UTF32-H-Behfouj5.bcmap +0 -0
  307. package/engine/dist/web/assets/UniGB-UTF32-V-BFm4myiz.bcmap +0 -0
  308. package/engine/dist/web/assets/UniGB-UTF8-H-EROft1ro.bcmap +0 -0
  309. package/engine/dist/web/assets/UniGB-UTF8-V-C8SGUjFI.bcmap +0 -0
  310. package/engine/dist/web/assets/UniJIS-UCS2-H-2DbZ0MaQ.bcmap +0 -0
  311. package/engine/dist/web/assets/UniJIS-UCS2-HW-H-BfJ3t__Z.bcmap +0 -0
  312. package/engine/dist/web/assets/UniJIS-UCS2-HW-V-BZyQaeYZ.bcmap +0 -0
  313. package/engine/dist/web/assets/UniJIS-UCS2-V-Cr3xs5m4.bcmap +0 -0
  314. package/engine/dist/web/assets/UniJIS-UTF16-H-BlDDPdCK.bcmap +0 -0
  315. package/engine/dist/web/assets/UniJIS-UTF16-V-DfK_Aqy1.bcmap +0 -0
  316. package/engine/dist/web/assets/UniJIS-UTF32-H-BIQPWkjv.bcmap +0 -0
  317. package/engine/dist/web/assets/UniJIS-UTF32-V-0rh54ED3.bcmap +0 -0
  318. package/engine/dist/web/assets/UniJIS-UTF8-H-CGusholx.bcmap +0 -0
  319. package/engine/dist/web/assets/UniJIS-UTF8-V-BGWFHinB.bcmap +0 -0
  320. package/engine/dist/web/assets/UniJIS2004-UTF16-H-KXixH-HI.bcmap +0 -0
  321. package/engine/dist/web/assets/UniJIS2004-UTF16-V-BSHgoaRy.bcmap +0 -0
  322. package/engine/dist/web/assets/UniJIS2004-UTF32-H-CXrD-9YE.bcmap +0 -0
  323. package/engine/dist/web/assets/UniJIS2004-UTF32-V-CH3k_ouF.bcmap +0 -0
  324. package/engine/dist/web/assets/UniJIS2004-UTF8-H-BkOctZjn.bcmap +0 -0
  325. package/engine/dist/web/assets/UniJIS2004-UTF8-V-D8AJhywu.bcmap +0 -0
  326. package/engine/dist/web/assets/UniJISPro-UCS2-HW-V-DLNkNO1x.bcmap +0 -0
  327. package/engine/dist/web/assets/UniJISPro-UCS2-V-BpvX7JBA.bcmap +0 -0
  328. package/engine/dist/web/assets/UniJISPro-UTF8-V-Db5K6XXE.bcmap +0 -0
  329. package/engine/dist/web/assets/UniJISX0213-UTF32-H-BavAhZjb.bcmap +0 -0
  330. package/engine/dist/web/assets/UniJISX0213-UTF32-V-Cz7JBDr9.bcmap +0 -0
  331. package/engine/dist/web/assets/UniJISX02132004-UTF32-H-DAH-AgUq.bcmap +0 -0
  332. package/engine/dist/web/assets/UniJISX02132004-UTF32-V-BqqDIAe5.bcmap +0 -0
  333. package/engine/dist/web/assets/UniKS-UCS2-H-YeUc7k1L.bcmap +0 -0
  334. package/engine/dist/web/assets/UniKS-UCS2-V-ahlksFWQ.bcmap +0 -0
  335. package/engine/dist/web/assets/UniKS-UTF16-H-B0dvB-Td.bcmap +0 -0
  336. package/engine/dist/web/assets/UniKS-UTF16-V-EgvK-6IG.bcmap +0 -0
  337. package/engine/dist/web/assets/UniKS-UTF32-H-olR_LtmR.bcmap +0 -0
  338. package/engine/dist/web/assets/UniKS-UTF32-V-CnOTbPsn.bcmap +0 -0
  339. package/engine/dist/web/assets/UniKS-UTF8-H-CbpebD-y.bcmap +0 -0
  340. package/engine/dist/web/assets/UniKS-UTF8-V-BIFFPWWm.bcmap +0 -0
  341. package/engine/dist/web/assets/V-BJkwLeFB.bcmap +0 -0
  342. package/engine/dist/web/assets/WP-Symbol-C3zzKJnO.bcmap +0 -0
  343. package/engine/dist/web/assets/{index-DGkq8xQx.css → index-CkL-mf_b.css} +1 -1
  344. package/engine/dist/web/assets/{index-DBYJK8Uy.js → index-e1oe-qOQ.js} +4 -4
  345. package/engine/dist/web/index.html +2 -2
  346. package/engine/src/web/FullPaper.tsx +14 -5
  347. package/engine/src/web/pdf-cmaps.ts +20 -0
  348. package/engine/src/web/review.tsx +31 -9
  349. package/engine/src/web/style.css +100 -28
  350. package/package.json +1 -1
  351. package/src/server/app.ts +4 -8
  352. package/src/server/manuscript.ts +38 -20
  353. package/src/server/store.ts +26 -1
  354. package/src/server/workspace.ts +15 -10
  355. package/dist/assets/index-BIc9Mnim.css +0 -1
  356. package/dist/assets/index-MvQvvv3I.js +0 -267
  357. package/dist/assets/pdf-view-ByftRBhD.js +0 -598
  358. package/engine/dist/web/assets/FullPaper-DWwiT43p.js +0 -54
@@ -9,8 +9,8 @@
9
9
  rel="icon"
10
10
  href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%232b6e5c'/%3E%3Cpath d='M4 8.5l2.5 2.5L12 5' stroke='white' stroke-width='2' fill='none'/%3E%3C/svg%3E"
11
11
  />
12
- <script type="module" crossorigin src="/assets/index-DBYJK8Uy.js"></script>
13
- <link rel="stylesheet" crossorigin href="/assets/index-DGkq8xQx.css">
12
+ <script type="module" crossorigin src="/assets/index-e1oe-qOQ.js"></script>
13
+ <link rel="stylesheet" crossorigin href="/assets/index-CkL-mf_b.css">
14
14
  </head>
15
15
  <body>
16
16
  <div id="root"></div>
@@ -14,12 +14,21 @@ import worker from "pdfjs-dist/build/pdf.worker.min.mjs?url";
14
14
  import type { ChangeBlock, Strip } from "../types.ts";
15
15
  import { t } from "./i18n.ts";
16
16
  import type { PlacedChange } from "./paper.ts";
17
+ import { cMapData } from "./pdf-cmaps.ts";
17
18
 
18
19
  // Built assets share one folder, and a host such as Papilot serves the viewer
19
- // under its own prefix: find the worker next to this chunk, not at /assets.
20
- pdfjs.GlobalWorkerOptions.workerSrc = import.meta.env.DEV
21
- ? worker
22
- : new URL(worker.split("/").pop()!, import.meta.url).href;
20
+ // under its own prefix: find the worker and CMaps next to this chunk, not at /assets.
21
+ const besideChunk = (url: string) =>
22
+ import.meta.env.DEV ? url : new URL(url.split("/").pop()!, import.meta.url).href;
23
+ pdfjs.GlobalWorkerOptions.workerSrc = besideChunk(worker);
24
+ const CMapData = cMapData(
25
+ import.meta.glob<string>("../../node_modules/pdfjs-dist/cmaps/*.bcmap", {
26
+ eager: true,
27
+ import: "default",
28
+ query: "?url&no-inline",
29
+ }),
30
+ besideChunk,
31
+ );
23
32
 
24
33
  // The whole paper with its changes in place: the new PDF's pages, opened up
25
34
  // where a change's new lines are, and that change shown there as in the
@@ -134,7 +143,7 @@ export function FullPaper({
134
143
  setStarted(Date.now());
135
144
  announced.current = false;
136
145
  const open = async (source: string, partial: boolean) => {
137
- const task = pdfjs.getDocument({ url: source, enableXfa: false });
146
+ const task = pdfjs.getDocument({ url: source, enableXfa: false, BinaryDataFactory: CMapData });
138
147
  tasks.push(task);
139
148
  const pdf = await task.promise,
140
149
  first = (await pdf.getPage(1)).getViewport({ scale: 1 }),
@@ -0,0 +1,20 @@
1
+ // pdf.js draws CJK text from fonts without a ToUnicode map, such as xeCJK's
2
+ // default Fandol fonts, through Adobe's predefined CMaps; without them those
3
+ // characters stay blank. Each bundle ships pdf.js's packed CMaps as assets and
4
+ // passes their URLs here. pdf.js then asks for one file at a time by name.
5
+ export function cMapData(urls: Record<string, string>, resolve = (url: string) => url) {
6
+ const files = new Map(
7
+ Object.entries(urls).map(([path, url]) => [path.slice(path.lastIndexOf("/") + 1), url]),
8
+ );
9
+ return class CMapData {
10
+ async fetch({ kind, filename }: { kind: string; filename: string }) {
11
+ // Standard fonts and wasm keep pdf.js's behaviour without their URLs.
12
+ if (kind !== "cMapUrl") throw new Error(`Ensure that the \`${kind}\` API parameter is provided.`);
13
+ const url = files.get(filename);
14
+ if (!url) throw new Error(`Unknown CMap: ${filename}`);
15
+ const response = await fetch(resolve(url));
16
+ if (!response.ok) throw new Error(`Unable to load CMap data at: ${resolve(url)}`);
17
+ return new Uint8Array(await response.arrayBuffer());
18
+ }
19
+ };
20
+ }
@@ -101,6 +101,12 @@ function store(key: string, value: unknown) {
101
101
  /* Storage is optional. */
102
102
  }
103
103
  }
104
+ // The comment box grows with its text up to the height its style allows.
105
+ function fitComposer(element: HTMLTextAreaElement | null) {
106
+ if (!element) return;
107
+ element.style.height = "0";
108
+ element.style.height = `${element.scrollHeight}px`;
109
+ }
104
110
  function jobText(job: PageState["job"]) {
105
111
  return job?.status === "queued"
106
112
  ? job.ahead
@@ -1671,33 +1677,49 @@ export function ReviewApp({
1671
1677
  void submitComposer();
1672
1678
  }}
1673
1679
  >
1674
- <label htmlFor="composer-text">
1675
- {t("Comment on change {n}", { n: composer.stop + 1 })}
1676
- </label>
1680
+ <div className="composer-head">
1681
+ <label htmlFor="composer-text">
1682
+ {t("Comment on change {n}", { n: composer.stop + 1 })}
1683
+ </label>
1684
+ <button
1685
+ type="button"
1686
+ className="composer-close"
1687
+ aria-label={t("Cancel")}
1688
+ title={t("Cancel")}
1689
+ onClick={() => closeComposer()}
1690
+ >
1691
+ <svg viewBox="0 0 24 24" aria-hidden="true">
1692
+ <path d="M18 6 6 18M6 6l12 12" />
1693
+ </svg>
1694
+ </button>
1695
+ </div>
1677
1696
  <textarea
1678
1697
  id="composer-text"
1698
+ ref={fitComposer}
1679
1699
  autoFocus
1680
- rows={4}
1700
+ rows={3}
1681
1701
  maxLength={20000}
1682
1702
  value={draft}
1683
1703
  onChange={(e) => {
1684
1704
  setDraft(e.target.value);
1685
1705
  store(composer.key, e.target.value);
1706
+ fitComposer(e.target);
1686
1707
  }}
1687
1708
  placeholder={t("What should the agent change here?")}
1688
1709
  />
1689
1710
  <div className="composer-row">
1690
1711
  <span className="muted">{t("⌘↵ send · Esc cancel")}</span>
1691
- <button type="button" onClick={() => closeComposer()}>
1692
- {t("Cancel")}
1693
- </button>
1694
1712
  <button
1695
1713
  type="submit"
1696
- title={state?.commentLock ? t(state.commentLock) : undefined}
1714
+ className="composer-send"
1715
+ aria-label={t("Send")}
1716
+ title={state?.commentLock ? t(state.commentLock) : t("Send")}
1697
1717
  aria-describedby={state?.commentLock ? "review-lock" : undefined}
1698
1718
  disabled={busy || !draft.trim() || Boolean(state?.commentLock)}
1699
1719
  >
1700
- {t("Send")}
1720
+ <svg viewBox="0 0 24 24" aria-hidden="true">
1721
+ <path d="M12 19V5m-7 7 7-7 7 7" />
1722
+ </svg>
1701
1723
  </button>
1702
1724
  </div>
1703
1725
  </form>
@@ -1274,57 +1274,129 @@ kbd {
1274
1274
  width: min(380px, calc(100vw - 24px));
1275
1275
  }
1276
1276
 
1277
+ /* The comment box: a title with a close button, a borderless field that
1278
+ grows with its text, and a round send button, like Papilot's own composer. */
1277
1279
  .composer {
1278
1280
  position: fixed;
1279
1281
  left: 50%;
1280
1282
  bottom: 84px;
1281
1283
  transform: translateX(-50%);
1282
1284
  z-index: 5;
1283
- width: min(540px, calc(100vw - 24px));
1284
- padding: 12px;
1285
+ width: min(560px, calc(100vw - 24px));
1285
1286
  background: var(--bar);
1286
1287
  border: 1px solid var(--line);
1287
- border-radius: 12px;
1288
- box-shadow: 0 6px 24px rgb(0 0 0 / 0.08);
1288
+ border-radius: 14px;
1289
+ box-shadow:
1290
+ 0 2px 4px rgb(0 0 0 / 0.04),
1291
+ 0 14px 36px rgb(0 0 0 / 0.13);
1289
1292
  }
1290
- .composer label {
1291
- display: block;
1292
- font-size: 17px;
1293
- font-weight: 400;
1294
- color: var(--ink);
1295
- margin-bottom: 6px;
1293
+ .composer-head {
1294
+ display: flex;
1295
+ align-items: center;
1296
+ gap: 8px;
1297
+ padding: 8px 8px 0 16px;
1298
+ }
1299
+ .composer-head label {
1300
+ flex: 1;
1301
+ min-width: 0;
1302
+ overflow: hidden;
1303
+ font-size: 13px;
1304
+ font-weight: 600;
1305
+ color: var(--muted);
1306
+ text-overflow: ellipsis;
1307
+ white-space: nowrap;
1296
1308
  }
1297
1309
  .composer textarea {
1310
+ display: block;
1298
1311
  width: 100%;
1299
- resize: vertical;
1300
- min-height: 84px;
1301
- padding: 8px 10px;
1302
- border: 1px solid var(--line);
1303
- border-radius: 7px;
1304
- background: var(--paper);
1305
- color: #1f2328;
1312
+ min-height: 70px;
1313
+ max-height: min(280px, 40dvh);
1314
+ margin: 0;
1315
+ padding: 4px 16px;
1316
+ border: 0;
1317
+ outline: none;
1318
+ resize: none;
1319
+ overflow-y: auto;
1320
+ background: transparent;
1321
+ color: var(--ink);
1322
+ font-size: 15px;
1323
+ line-height: 1.55;
1306
1324
  }
1307
- .composer textarea:focus {
1308
- outline: 2px solid color-mix(in srgb, var(--comment) 45%, transparent);
1309
- border-color: transparent;
1325
+ .composer textarea::placeholder {
1326
+ color: var(--muted);
1310
1327
  }
1311
1328
  .composer-row {
1312
1329
  display: flex;
1313
1330
  align-items: center;
1314
1331
  justify-content: space-between;
1315
- margin-top: 8px;
1332
+ gap: 8px;
1333
+ padding: 6px 8px 8px 16px;
1316
1334
  font-size: 12px;
1317
1335
  }
1318
- .composer-row kbd {
1319
- margin-left: 0;
1336
+ .composer-row .muted {
1337
+ min-width: 0;
1320
1338
  }
1321
- .composer button {
1322
- border: 1px solid var(--line);
1323
- border-radius: 7px;
1324
- padding: 6px 16px;
1325
- background: var(--paper);
1339
+ .composer-close,
1340
+ .composer-send {
1341
+ display: inline-flex;
1342
+ align-items: center;
1343
+ justify-content: center;
1344
+ flex: none;
1345
+ padding: 0;
1346
+ border: 0;
1347
+ }
1348
+ .composer svg {
1349
+ width: 16px;
1350
+ height: 16px;
1351
+ fill: none;
1352
+ stroke: currentColor;
1353
+ stroke-width: 2;
1354
+ stroke-linecap: round;
1355
+ stroke-linejoin: round;
1356
+ }
1357
+ .composer-close {
1358
+ width: 28px;
1359
+ height: 28px;
1360
+ border-radius: 8px;
1361
+ background: transparent;
1362
+ color: var(--muted);
1363
+ transition:
1364
+ background-color 0.15s,
1365
+ color 0.15s;
1366
+ }
1367
+ .composer-close svg {
1368
+ width: 14px;
1369
+ height: 14px;
1370
+ }
1371
+ .composer-close:hover {
1372
+ background: color-mix(in srgb, var(--ink) 7%, transparent);
1326
1373
  color: var(--ink);
1327
1374
  }
1375
+ .composer-send {
1376
+ width: 32px;
1377
+ height: 32px;
1378
+ border-radius: 50%;
1379
+ background: var(--accent);
1380
+ color: #fff;
1381
+ transition:
1382
+ background-color 0.15s,
1383
+ transform 0.1s;
1384
+ }
1385
+ .composer-send svg {
1386
+ width: 18px;
1387
+ height: 18px;
1388
+ }
1389
+ .composer-send:hover:not(:disabled) {
1390
+ background: color-mix(in srgb, var(--accent) 85%, #000);
1391
+ }
1392
+ .composer-send:active:not(:disabled) {
1393
+ transform: scale(0.94);
1394
+ }
1395
+ .composer-send:disabled {
1396
+ opacity: 1;
1397
+ background: color-mix(in srgb, var(--ink) 9%, transparent);
1398
+ color: var(--muted);
1399
+ }
1328
1400
 
1329
1401
  .toast {
1330
1402
  position: fixed;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "papilot",
3
- "version": "0.8.7",
3
+ "version": "0.8.9",
4
4
  "description": "A Cursor-like workspace for your papers: annotate and review PDFs directly without ever touching LaTeX.",
5
5
  "keywords": [
6
6
  "latex",
package/src/server/app.ts CHANGED
@@ -245,15 +245,11 @@ export async function buildApp(options: AppOptions) {
245
245
  // A skill submission uses the same task endpoint and retry identity as editing.
246
246
  const existing = store.tasks().find(task => task.id === id);
247
247
  if (existing) { if (existing.projectId !== projectId) throw new Error('Invalid task ID.'); return existing; }
248
- let reviewInput;
249
- if (skill && findSkill(store.project(projectId).path, skill).output === 'annotations') {
250
- const state = await manuscripts.state(projectId, store.project(projectId).path);
251
- if (!state.build.current || !state.build.key) throw new Error('Wait for the current PDF to finish compiling, then run the AI review skill.');
252
- reviewInput = await manuscripts.reviewSnapshot(projectId, state.build.key);
253
- }
254
- return workspace.createTask({ id, projectId, reviewInput, ...(reviewInput ? { prepareReview: (worktree: string) => manuscripts.captureReview(store.project(projectId).path, worktree, id) } : {}), title: string(b.title, 'Task title', 120), instructions: skill || annotations?.length || attachments.length ? optional(b.instructions, 'Instructions') : string(b.instructions, 'Instructions'), skill, annotations, groupInstructions, attachments, agent: agent(b.agent), model: typeof b.model === 'string' ? b.model : undefined, effort: effort(b.effort), serviceTier: b.serviceTier === undefined ? undefined : string(b.serviceTier, 'Service tier', 64), author: author(request.viewer) });
248
+ // AI review compiles the task's own copy of the manuscript, so it does not wait for the preview.
249
+ const reviewing = Boolean(skill && findSkill(store.project(projectId).path, skill).output === 'annotations');
250
+ return workspace.createTask({ id, projectId, ...(reviewing ? { prepareReview: (worktree: string) => manuscripts.captureReview(store.project(projectId).path, worktree, id) } : {}), title: string(b.title, 'Task title', 120), instructions: skill || annotations?.length || attachments.length ? optional(b.instructions, 'Instructions') : string(b.instructions, 'Instructions'), skill, annotations, groupInstructions, attachments, agent: agent(b.agent), model: typeof b.model === 'string' ? b.model : undefined, effort: effort(b.effort), serviceTier: b.serviceTier === undefined ? undefined : string(b.serviceTier, 'Service tier', 64), author: author(request.viewer) });
255
251
  });
256
- app.get<{ Params: { id: string } }>('/api/tasks/:id/items', task, request => { store.task(request.params.id); return { items: store.items(request.params.id) }; });
252
+ app.get<{ Params: { id: string }; Querystring: { since?: string } }>('/api/tasks/:id/items', task, request => { store.task(request.params.id); return store.itemsSince(request.params.id, typeof request.query.since === 'string' ? request.query.since : undefined); });
257
253
  app.get<{ Params: { id: string } }>('/api/tasks/:id/note-results', task, async request => {
258
254
  const task = store.task(request.params.id), client = await connectReview(store.project(task.projectId).path);
259
255
  return { notes: await taskNoteResults(task, store.items(task.id), store.runs().filter(run => run.taskId === task.id), client) };
@@ -79,6 +79,19 @@ function gitDir(root: string) {
79
79
  return fs.statSync(dot).isFile() ? path.resolve(root, readText(dot).replace(/^gitdir:\s*/, '').trim()) : dot;
80
80
  } catch { return null; }
81
81
  }
82
+ // Cheap evidence that Git's own state moved: the index, HEAD, the current
83
+ // branch, a fetch or a merge. Each changes when a commit, stage, checkout,
84
+ // fetch or merge happens, from Papilot or from a terminal.
85
+ export function gitStamp(root: string) {
86
+ const dir = gitDir(root);
87
+ if (!dir) return '';
88
+ const common = path.resolve(dir, readText(path.join(dir, 'commondir')).trim() || '.');
89
+ const branch = readText(path.join(dir, 'HEAD')).match(/^ref: (refs\/\S+)/)?.[1];
90
+ // FETCH_HEAD belongs to the worktree that fetched, so a linked worktree checks
91
+ // its own and the main one's.
92
+ return [path.join(dir, 'index'), path.join(dir, 'HEAD'), path.join(dir, 'MERGE_HEAD'), path.join(dir, 'FETCH_HEAD'), common !== dir && path.join(common, 'FETCH_HEAD'), path.join(common, 'packed-refs'), branch && path.join(common, branch)]
93
+ .map(file => { try { const stat = file && fs.statSync(file); return stat ? `${stat.mtimeMs}:${stat.size}` : '-'; } catch { return '-'; } }).join('|');
94
+ }
82
95
  export function mergeInProgress(root: string) {
83
96
  const dir = gitDir(root);
84
97
  return Boolean(dir && fs.existsSync(path.join(dir, 'MERGE_HEAD')));
@@ -298,13 +311,13 @@ async function pageSizes(mupdf: any, pdf: string) {
298
311
  }
299
312
 
300
313
  async function engine() {
301
- const [{ Builder }, { Repo, gitBinary }, { loadProject }, { mupdf, PdfDocument, containsCenter, walkText }] = await Promise.all([
314
+ const [{ Builder, BuildError }, { Repo, gitBinary }, { loadProject }, { mupdf, PdfDocument, containsCenter, walkText }] = await Promise.all([
302
315
  import(pathToFileURL(path.join(ENGINE, 'src/core/build.ts')).href),
303
316
  import(pathToFileURL(path.join(ENGINE, 'src/core/git.ts')).href),
304
317
  import(pathToFileURL(path.join(ENGINE, 'src/core/config.ts')).href),
305
318
  import(pathToFileURL(path.join(ENGINE, 'src/render/pdf.ts')).href),
306
319
  ]);
307
- return { Builder, Repo, gitBinary, loadProject, mupdf, PdfDocument, containsCenter, walkText };
320
+ return { Builder, BuildError, Repo, gitBinary, loadProject, mupdf, PdfDocument, containsCenter, walkText };
308
321
  }
309
322
 
310
323
  // `synctex edit` maps a PDF position (big points from the top-left corner) to
@@ -483,12 +496,16 @@ export class Manuscripts extends EventEmitter {
483
496
  // The view polls every second and a half while a build runs; Git's status
484
497
  // costs a walk of the working tree, which competes with TeX, so it is
485
498
  // read again at most every few seconds then, and after Papilot's own writes.
486
- private statuses = new Map<string, { at: number; value: Promise<GitStatus> }>();
487
- private gitStatus(root: string, building: boolean) {
488
- const cached = this.statuses.get(root);
489
- if (building && cached && Date.now() - cached.at < 5000) return cached.value;
499
+ // Between builds the view polls every five seconds to notice other editors:
500
+ // the status is read again when the working tree or Git's own state moved,
501
+ // and at least every half minute for what neither shows, such as a push
502
+ // from a terminal changing how far the branch is ahead.
503
+ private statuses = new Map<string, { at: number; value: Promise<GitStatus>; tree?: string; stamp?: string }>();
504
+ private gitStatus(root: string, building: boolean, tree?: string) {
505
+ const cached = this.statuses.get(root), age = cached ? Date.now() - cached.at : Infinity, stamp = building ? undefined : gitStamp(root);
506
+ if (cached && (building ? age < 5000 : tree !== undefined && cached.tree === tree && cached.stamp === stamp && age < 30000)) return cached.value;
490
507
  const value = gitStatus(root);
491
- this.statuses.set(root, { at: Date.now(), value });
508
+ this.statuses.set(root, { at: Date.now(), value, tree, stamp });
492
509
  value.catch(() => { if (this.statuses.get(root)?.value === value) this.statuses.delete(root); });
493
510
  return value;
494
511
  }
@@ -497,7 +514,8 @@ export class Manuscripts extends EventEmitter {
497
514
  const current = this.builds.get(projectId), building = current?.status === 'building';
498
515
  // Polling an in-flight build needs its status, not another source snapshot.
499
516
  // Once it finishes, the next read checks for edits made during compilation.
500
- const [status, tree] = await Promise.all([this.gitStatus(root, building), building ? current.tree : this.snapshot(root)]);
517
+ // The snapshot comes first so an unchanged tree can reuse the last status.
518
+ const tree = building ? current.tree : await this.snapshot(root), status = await this.gitStatus(root, building, building ? undefined : tree);
501
519
  let build = this.builds.get(projectId);
502
520
  if (!build || (build.status !== 'building' && (build.tree !== tree || rebuild))) build = this.start(projectId, root, tree, build, rebuild);
503
521
  const { status: state, key, pages, builtAt, error } = build;
@@ -701,13 +719,17 @@ export class Manuscripts extends EventEmitter {
701
719
  }
702
720
 
703
721
  // Compile the task's frozen source tree, then retain its PDF outside the build cache.
722
+ // A preview build of the same tree is shared, not repeated.
704
723
  async captureReview(root: string, worktree: string, taskId: string): Promise<ReviewSnapshot> {
705
- const { Builder, Repo, loadProject } = await engine();
724
+ const { Builder, BuildError, Repo, loadProject } = await engine();
706
725
  const tree = await workingTree(worktree);
707
- const result = await new Builder(loadProject(root), new Repo(root)).build(tree, ['full']);
708
- const pdf = await this.preserveReviewPdf(root, taskId, result.pdf);
709
- const key = createHash('sha256').update(fs.readFileSync(pdf)).digest('hex').slice(0, 16);
710
- return { ...await this.snapshotPdf(pdf, key), pdf };
726
+ let result;
727
+ try { result = await new Builder(loadProject(root), new Repo(root)).build(tree, ['full']); }
728
+ catch (error) { throw error instanceof BuildError ? new Error('The manuscript could not be compiled for AI review. Fix the errors shown in the manuscript preview, then try again.') : error; }
729
+ const key = createHash('sha256').update(fs.readFileSync(result.pdf)).digest('hex').slice(0, 16);
730
+ const snapshot = await this.snapshotPdf(result.pdf, key);
731
+ // The task does not exist yet, so a PDF kept by an earlier failed attempt is replaced.
732
+ return { ...snapshot, pdf: await this.preserveReviewPdf(root, taskId, result.pdf, true) };
711
733
  }
712
734
 
713
735
  private async reviewPdfPath(root: string, taskId: string) {
@@ -720,11 +742,11 @@ export class Manuscripts extends EventEmitter {
720
742
  return fs.existsSync(pdf) ? pdf : null;
721
743
  }
722
744
 
723
- async preserveReviewPdf(root: string, taskId: string, source: string) {
745
+ async preserveReviewPdf(root: string, taskId: string, source: string, replace = false) {
724
746
  const pdf = await this.reviewPdfPath(root, taskId), folder = path.dirname(pdf);
725
747
  fs.mkdirSync(folder, { recursive: true });
726
748
  // A retry always uses the original task copy, even after its build cache is pruned.
727
- if (!fs.existsSync(pdf)) { const temporary = path.join(folder, 'review.tmp'); fs.copyFileSync(source, temporary); fs.renameSync(temporary, pdf); }
749
+ if (replace || !fs.existsSync(pdf)) { const temporary = path.join(folder, 'review.tmp'); fs.copyFileSync(source, temporary); fs.renameSync(temporary, pdf); }
728
750
  return pdf;
729
751
  }
730
752
 
@@ -762,11 +784,7 @@ export class Manuscripts extends EventEmitter {
762
784
  } finally { original.close(); current.close(); }
763
785
  }
764
786
 
765
- async reviewSnapshot(projectId: string, key: string): Promise<ReviewSnapshot> {
766
- return this.snapshotPdf(this.pdf(projectId, key), key);
767
- }
768
-
769
- private async snapshotPdf(pdf: string, key: string): Promise<ReviewSnapshot> {
787
+ async snapshotPdf(pdf: string, key: string): Promise<ReviewSnapshot> {
770
788
  const { PdfDocument } = await engine();
771
789
  const document = new PdfDocument(pdf), lines: ReviewSnapshot['lines'] = [];
772
790
  let length = 0;
@@ -1,6 +1,7 @@
1
1
  import type { ReviewSnapshot } from './ai-review.ts';
2
2
  import { DatabaseSync } from 'node:sqlite';
3
3
  import { EventEmitter } from 'node:events';
4
+ import { randomUUID } from 'node:crypto';
4
5
  import fs from 'node:fs';
5
6
  import path from 'node:path';
6
7
  import type { Annotation, Project, Task, Run, TimelineItem } from '../shared.ts';
@@ -39,6 +40,7 @@ export class Store extends EventEmitter {
39
40
  saveProject(p: Project) { this.db.prepare('INSERT INTO projects VALUES(?,?,?) ON CONFLICT(id) DO UPDATE SET path=excluded.path,data=excluded.data').run(p.id, p.path, JSON.stringify(p)); this.emit('change'); }
40
41
  removeProject(id: string) {
41
42
  this.project(id);
43
+ const tasks = (this.db.prepare('SELECT id FROM tasks WHERE project_id=?').all(id) as { id: string }[]).map(row => row.id);
42
44
  this.transaction(() => {
43
45
  // Only Papilot records are removed. Never finish review tasks or touch their files.
44
46
  this.db.prepare('INSERT OR IGNORE INTO deleted_tasks SELECT id, ? FROM tasks WHERE project_id=?').run(Date.now(), id);
@@ -46,6 +48,7 @@ export class Store extends EventEmitter {
46
48
  for (const table of ['tasks', 'annotations', 'members']) this.db.prepare(`DELETE FROM ${table} WHERE project_id=?`).run(id);
47
49
  this.db.prepare('DELETE FROM projects WHERE id=?').run(id);
48
50
  });
51
+ for (const task of tasks) this.itemRevisions.delete(task);
49
52
  this.emit('change');
50
53
  }
51
54
  saveTask(t: Task) { t.updatedAt = Date.now(); this.db.prepare('INSERT INTO tasks VALUES(?,?,?) ON CONFLICT(id) DO UPDATE SET data=excluded.data').run(t.id, t.projectId, JSON.stringify(t)); this.emit('change'); return t; }
@@ -53,7 +56,28 @@ export class Store extends EventEmitter {
53
56
  saveRun(r: Run) { this.db.prepare('INSERT INTO runs VALUES(?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET status=excluded.status,data=excluded.data').run(r.id, r.taskId, r.requestKey, r.status, JSON.stringify(r)); this.emit('change'); }
54
57
  runByKey(key: string) { const row = this.db.prepare('SELECT data FROM runs WHERE request_key=?').get(key) as { data: string } | undefined; return row ? JSON.parse(row.data) as Run : undefined; }
55
58
  items(taskId: string) { return (this.db.prepare('SELECT data FROM items WHERE task_id=? ORDER BY created,rowid').all(taskId) as { data: string }[]).map(r => JSON.parse(r.data) as TimelineItem); }
56
- saveItem(item: TimelineItem) { this.db.prepare('INSERT INTO items VALUES(?,?,?,?) ON CONFLICT(id) DO UPDATE SET data=excluded.data').run(item.id, item.taskId, item.createdAt, JSON.stringify(item)); this.emit('change'); }
59
+ // A running task's view reads its timeline every second, and a command's
60
+ // output alone can be 40 KB, so a view that has the timeline asks only for
61
+ // the items written since its revision. Revisions start over with the
62
+ // process; one from an earlier run gets the whole timeline.
63
+ private readonly boot = randomUUID().slice(0, 8);
64
+ private revision = 0;
65
+ private itemRevisions = new Map<string, Map<string, number>>();
66
+ itemsSince(taskId: string, since?: string): { items: TimelineItem[]; revision: string; partial?: true } {
67
+ const revision = `${this.boot}:${this.revision}`, match = /^([0-9a-f]{8}):(\d{1,15})$/.exec(since || ''), after = match ? Number(match[2]) : NaN;
68
+ if (!match || match[1] !== this.boot || after > this.revision) return { items: this.items(taskId), revision };
69
+ const changed = [...this.itemRevisions.get(taskId) || []].filter(([, at]) => at > after).map(([id]) => id);
70
+ if (changed.length > 500) return { items: this.items(taskId), revision };
71
+ const items = changed.length ? (this.db.prepare(`SELECT data FROM items WHERE task_id=? AND id IN (${changed.map(() => '?').join()}) ORDER BY created,rowid`).all(taskId, ...changed) as { data: string }[]).map(r => JSON.parse(r.data) as TimelineItem) : [];
72
+ return { items, revision, partial: true };
73
+ }
74
+ saveItem(item: TimelineItem) {
75
+ this.db.prepare('INSERT INTO items VALUES(?,?,?,?) ON CONFLICT(id) DO UPDATE SET data=excluded.data').run(item.id, item.taskId, item.createdAt, JSON.stringify(item));
76
+ let revisions = this.itemRevisions.get(item.taskId);
77
+ if (!revisions) this.itemRevisions.set(item.taskId, revisions = new Map());
78
+ revisions.set(item.id, ++this.revision);
79
+ this.emit('change');
80
+ }
57
81
  saveReviewInput(taskId: string, snapshot: ReviewSnapshot) { this.db.prepare('INSERT INTO ai_review_inputs VALUES(?,?) ON CONFLICT(task_id) DO UPDATE SET data=excluded.data').run(taskId, JSON.stringify(snapshot)); }
58
82
  reviewInput(taskId: string): ReviewSnapshot {
59
83
  const row = this.db.prepare('SELECT data FROM ai_review_inputs WHERE task_id=?').get(taskId) as { data: string } | undefined;
@@ -81,6 +105,7 @@ export class Store extends EventEmitter {
81
105
  this.db.prepare('DELETE FROM ai_review_inputs WHERE task_id=?').run(id);
82
106
  this.db.prepare('DELETE FROM tasks WHERE id=?').run(id);
83
107
  });
108
+ this.itemRevisions.delete(id);
84
109
  this.emit('change');
85
110
  }
86
111
  transaction<T>(fn: () => T): T { this.db.exec('BEGIN IMMEDIATE'); try { const result = fn(); this.db.exec('COMMIT'); return result; } catch (e) { this.db.exec('ROLLBACK'); throw e; } }
@@ -130,7 +130,7 @@ export class Workspace extends EventEmitter {
130
130
  || this.store.tasks().some(task => ['queued', 'running', 'waiting_input', 'building', 'uncertain', 'deleting'].includes(task.status) || task.pendingReview));
131
131
  }
132
132
  readonly attachments: Attachments;
133
- constructor(public store: Store, public codex: AgentRuntime = new Codex(), private concurrency = 2, private connectReview = reviewClient, others: Partial<Record<AgentId, AgentRuntime>> = {}) {
133
+ constructor(public store: Store, public codex: AgentRuntime = new Codex(), private connectReview = reviewClient, others: Partial<Record<AgentId, AgentRuntime>> = {}) {
134
134
  super();
135
135
  this.attachments = new Attachments(store);
136
136
  this.agents = { codex, claude: others.claude || new ClaudeAgent(), cursor: others.cursor || new CursorAgent(), copilot: others.copilot || new AcpAgent(ACP_PROFILES.copilot), grok: others.grok || new AcpAgent(ACP_PROFILES.grok), antigravity: others.antigravity || new AntigravityAgent(),
@@ -281,14 +281,15 @@ export class Workspace extends EventEmitter {
281
281
  }
282
282
  // A task combines the author's words, an optional skill and selected PDF notes.
283
283
  annotationSending(id: string) { return this.preparingNotes.has(id); }
284
- async createTask(input: { reviewInput?: ReviewSnapshot; prepareReview?: (worktree: string) => Promise<ReviewSnapshot>; projectId: string; title: string; instructions: string; skill?: string; annotations?: string[]; groupInstructions?: GroupInstructions; attachments?: string[]; agent?: AgentId; model?: string; effort?: string; serviceTier?: string; id: string; author?: Author }) {
284
+ // An AI review passes prepareReview, which reads the PDF of the new task's manuscript copy.
285
+ async createTask(input: { prepareReview?: (worktree: string) => Promise<ReviewSnapshot>; projectId: string; title: string; instructions: string; skill?: string; annotations?: string[]; groupInstructions?: GroupInstructions; attachments?: string[]; agent?: AgentId; model?: string; effort?: string; serviceTier?: string; id: string; author?: Author }) {
285
286
  if (!/^[\w-]{16,64}$/.test(input.id)) throw new Error('Invalid task ID.');
286
287
  if (this.store.taskDeleted(input.id)) throw new Error('This task was deleted. Create a new task instead.');
287
288
  // A retried request returns its task; an ID from another paper is refused.
288
289
  const existing = this.store.tasks().find(t => t.id === input.id);
289
- if (existing) { if (existing.projectId !== input.projectId || (existing.mode === 'review') !== Boolean(input.reviewInput)) throw new Error('Invalid task ID.'); return existing; }
290
- if (input.reviewInput && !REVIEW_AGENTS.includes(input.agent || 'codex')) throw new Error('Choose Codex, Claude or GLM for AI review.');
291
- if (input.reviewInput && (input.annotations?.length || input.attachments?.length)) throw new Error('AI review uses the manuscript PDF. Start a separate editing task to send notes or attachments.');
290
+ if (existing) { if (existing.projectId !== input.projectId || (existing.mode === 'review') !== Boolean(input.prepareReview)) throw new Error('Invalid task ID.'); return existing; }
291
+ if (input.prepareReview && !REVIEW_AGENTS.includes(input.agent || 'codex')) throw new Error('Choose Codex, Claude or GLM for AI review.');
292
+ if (input.prepareReview && (input.annotations?.length || input.attachments?.length)) throw new Error('AI review uses the manuscript PDF. Start a separate editing task to send notes or attachments.');
292
293
  if (input.serviceTier !== undefined) {
293
294
  if (input.agent && input.agent !== 'codex') throw new Error('Service tiers are only available for Codex.');
294
295
  const models = this.runtimes.codex.models;
@@ -301,7 +302,7 @@ export class Workspace extends EventEmitter {
301
302
  let releaseAttachments = () => {};
302
303
  try {
303
304
  const project = this.store.project(input.projectId), skill = input.skill ? findSkill(project.path, input.skill) : undefined;
304
- if (skill?.output === 'annotations' && !input.reviewInput) throw new Error('Start a new task to run the AI review skill on the current PDF.');
305
+ if (skill?.output === 'annotations' && !input.prepareReview) throw new Error('Start a new task to run the AI review skill on the current PDF.');
305
306
  const files = this.attachments.resolve(project.id, input.attachments || [], input.author?.id || 'owner', input.id);
306
307
  releaseAttachments = this.attachments.reserve(project.id, input.attachments || [], input.author?.id || 'owner', input.id);
307
308
  const available = new Map(this.store.annotations(project.id).filter(note => !note.taskId && !isTaskReviewNote(note)).map(note => [note.id, note]));
@@ -316,12 +317,17 @@ export class Workspace extends EventEmitter {
316
317
  if (Object.keys(groupInstructions).some(group => !selectedGroups.has(group as keyof GroupInstructions))) throw new Error('Group instructions must refer to selected PDF notes.');
317
318
  if (groupsWithoutInstructions(notes, groupInstructions).length && !input.instructions.trim() && !skill) throw new Error('Describe what to do with each selected color group.');
318
319
  claimed = notes.map(note => note.id); claimed.forEach(id => this.preparingNotes.add(id));
319
- if (!input.reviewInput && !skill && !notes.length && !files.length && !input.instructions.trim()) throw new Error('Describe the revision, choose a skill, or add a PDF note.');
320
+ if (!input.prepareReview && !skill && !notes.length && !files.length && !input.instructions.trim()) throw new Error('Describe the revision, choose a skill, or add a PDF note.');
320
321
  const instructions = notes.length ? notesInstructions(notes, input.instructions, groupInstructions) : input.instructions;
321
322
  const client = await this.connectReview(project.path);
322
323
  const name = 'p-' + input.id.toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 48);
323
324
  const info = await client.post('/api/tasks', { name, title: input.title, deferred: true });
324
- const reviewInput = input.reviewInput && (input.prepareReview ? await input.prepareReview(info.worktree) : input.reviewInput);
325
+ let reviewInput: ReviewSnapshot | undefined;
326
+ if (input.prepareReview) try { reviewInput = await input.prepareReview(info.worktree); } catch (error) {
327
+ // Discard the copy, so a retry copies the manuscript again with the author's fixes.
328
+ await client.post(`${taskRoute(name)}/finish`, { force: true }).catch(() => undefined);
329
+ throw error;
330
+ }
325
331
  const task: Task = { ...(reviewInput ? { mode: 'review' as const, aiReview: { key: reviewInput.key } } : {}), id: input.id, projectId: project.id, name, title: input.title, titleSource: 'prompt', instructions: skill ? skillPrompt(skill, instructions) : instructions,
326
332
  worktree: info.worktree, deferred: info.deferred === true, threadId: null, turnId: null, agent: input.agent || 'codex', model: input.model || null, effort: input.effort || null, ...(input.serviceTier ? { serviceTier: input.serviceTier } : {}), status: 'idle',
327
333
  createdAt: Date.now(), updatedAt: Date.now(), feedbackCursor: 0, error: null, review: null };
@@ -329,7 +335,7 @@ export class Workspace extends EventEmitter {
329
335
  this.store.saveTask(task);
330
336
  if (reviewInput) this.store.saveReviewInput(task.id, reviewInput);
331
337
  });
332
- await this.enqueue(task.id, input.instructions || (input.reviewInput && !skill ? 'Review this manuscript and suggest improvements.' : ''), 'initial-' + task.id, false, { skill, notes, groupInstructions, attachments: files.map(file => file.id), author: input.author });
338
+ await this.enqueue(task.id, input.instructions || (input.prepareReview && !skill ? 'Review this manuscript and suggest improvements.' : ''), 'initial-' + task.id, false, { skill, notes, groupInstructions, attachments: files.map(file => file.id), author: input.author });
333
339
  // Naming is independent of the editing turn. An unavailable helper must
334
340
  // not fail creation or prevent the queued task from running.
335
341
  try { this.requestTaskTitle(task.id); } catch { /* Keep the prompt-derived title. */ }
@@ -407,7 +413,6 @@ export class Workspace extends EventEmitter {
407
413
  private async drain() {
408
414
  if (this.closing) return;
409
415
  for (const run of this.store.runs().filter(r => r.status === 'queued')) {
410
- if (this.active.size >= this.concurrency) break;
411
416
  const agent = this.agentOf(this.store.task(run.taskId));
412
417
  if (!this.agents[agent].ready || !this.runtimes[agent].authenticated) continue;
413
418
  if (this.active.has(run.taskId) || this.reviewWrites.has(run.taskId) || this.publishing.has(run.taskId) || this.store.task(run.taskId).delivery?.pending || BUSY.has(this.store.task(run.taskId).status)) continue;