@openspecui/web 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. package/dist/assets/abap-BdImnpbu.js +1 -0
  2. package/dist/assets/actionscript-3-CfeIJUat.js +1 -0
  3. package/dist/assets/ada-bCR0ucgS.js +1 -0
  4. package/dist/assets/andromeeda-C-Jbm3Hp.js +1 -0
  5. package/dist/assets/angular-html-CU67Zn6k.js +1 -0
  6. package/dist/assets/angular-ts-BwZT4LLn.js +1 -0
  7. package/dist/assets/apache-Pmp26Uib.js +1 -0
  8. package/dist/assets/apex-DDbsPZ6N.js +1 -0
  9. package/dist/assets/apl-B4CMkyY2.js +1 -0
  10. package/dist/assets/apl-dKokRX4l.js +1 -0
  11. package/dist/assets/applescript-Co6uUVPk.js +1 -0
  12. package/dist/assets/ara-BRHolxvo.js +1 -0
  13. package/dist/assets/asciiarmor-Df11BRmG.js +1 -0
  14. package/dist/assets/asciidoc-Dv7Oe6Be.js +1 -0
  15. package/dist/assets/asm-D_Q5rh1f.js +1 -0
  16. package/dist/assets/asn1-EdZsLKOL.js +1 -0
  17. package/dist/assets/asterisk-B-8jnY81.js +1 -0
  18. package/dist/assets/astro-CbQHKStN.js +1 -0
  19. package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
  20. package/dist/assets/awk-DMzUqQB5.js +1 -0
  21. package/dist/assets/ayu-dark-Cv9koXgw.js +1 -0
  22. package/dist/assets/ballerina-BFfxhgS-.js +1 -0
  23. package/dist/assets/bat-BkioyH1T.js +1 -0
  24. package/dist/assets/beancount-k_qm7-4y.js +1 -0
  25. package/dist/assets/berry-uYugtg8r.js +1 -0
  26. package/dist/assets/bibtex-CHM0blh-.js +1 -0
  27. package/dist/assets/bicep-Bmn6On1c.js +1 -0
  28. package/dist/assets/blade-DVc8C-J4.js +1 -0
  29. package/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  30. package/dist/assets/bsl-BO_Y6i37.js +1 -0
  31. package/dist/assets/c-BIGW1oBm.js +1 -0
  32. package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
  33. package/dist/assets/cairo-KRGpt6FW.js +1 -0
  34. package/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  35. package/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  36. package/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  37. package/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  38. package/dist/assets/clarity-D53aC0YG.js +1 -0
  39. package/dist/assets/clike-B9uivgTg.js +1 -0
  40. package/dist/assets/clojure-BMjYHr_A.js +1 -0
  41. package/dist/assets/clojure-P80f7IUj.js +1 -0
  42. package/dist/assets/cmake-BQqOBYOt.js +1 -0
  43. package/dist/assets/cmake-D1j8_8rp.js +1 -0
  44. package/dist/assets/cobol-CWcv1MsR.js +1 -0
  45. package/dist/assets/cobol-nwyudZeR.js +1 -0
  46. package/dist/assets/codeowners-Bp6g37R7.js +1 -0
  47. package/dist/assets/codeql-DsOJ9woJ.js +1 -0
  48. package/dist/assets/coffee-Ch7k5sss.js +1 -0
  49. package/dist/assets/coffeescript-S37ZYGWr.js +1 -0
  50. package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
  51. package/dist/assets/commonlisp-DBKNyK5s.js +1 -0
  52. package/dist/assets/coq-DkFqJrB1.js +1 -0
  53. package/dist/assets/cpp-CofmeUqb.js +1 -0
  54. package/dist/assets/crystal-SjHAIU92.js +1 -0
  55. package/dist/assets/crystal-tKQVLTB8.js +1 -0
  56. package/dist/assets/csharp-K5feNrxe.js +1 -0
  57. package/dist/assets/css-BnMrqG3P.js +1 -0
  58. package/dist/assets/css-DPfMkruS.js +1 -0
  59. package/dist/assets/csv-fuZLfV_i.js +1 -0
  60. package/dist/assets/cue-D82EKSYY.js +1 -0
  61. package/dist/assets/cypher-COkxafJQ.js +1 -0
  62. package/dist/assets/cypher-C_CwsFkJ.js +1 -0
  63. package/dist/assets/d-85-TOEBH.js +1 -0
  64. package/dist/assets/d-pRatUO7H.js +1 -0
  65. package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  66. package/dist/assets/dart-CF10PKvl.js +1 -0
  67. package/dist/assets/dax-CEL-wOlO.js +1 -0
  68. package/dist/assets/desktop-BmXAJ9_W.js +1 -0
  69. package/dist/assets/diff-D97Zzqfu.js +1 -0
  70. package/dist/assets/diff-DbItnlRl.js +1 -0
  71. package/dist/assets/docker-BcOcwvcX.js +1 -0
  72. package/dist/assets/dockerfile-BKs6k2Af.js +1 -0
  73. package/dist/assets/dotenv-Da5cRb03.js +1 -0
  74. package/dist/assets/dracula-BzJJZx-M.js +1 -0
  75. package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
  76. package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
  77. package/dist/assets/dtd-DF_7sFjM.js +1 -0
  78. package/dist/assets/dylan-DwRh75JA.js +1 -0
  79. package/dist/assets/ebnf-CDyGwa7X.js +1 -0
  80. package/dist/assets/ecl-Cabwm37j.js +1 -0
  81. package/dist/assets/edge-BkV0erSs.js +1 -0
  82. package/dist/assets/eiffel-CnydiIhH.js +1 -0
  83. package/dist/assets/elixir-CDX3lj18.js +1 -0
  84. package/dist/assets/elm-DbKCFpqz.js +1 -0
  85. package/dist/assets/elm-vLlmbW-K.js +1 -0
  86. package/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
  87. package/dist/assets/erb-BOJIQeun.js +1 -0
  88. package/dist/assets/erlang-BNw1qcRV.js +1 -0
  89. package/dist/assets/erlang-DsQrWhSR.js +1 -0
  90. package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
  91. package/dist/assets/everforest-light-C8M2exoo.js +1 -0
  92. package/dist/assets/factor-kuTfRLto.js +1 -0
  93. package/dist/assets/fcl-Kvtd6kyn.js +1 -0
  94. package/dist/assets/fennel-BYunw83y.js +1 -0
  95. package/dist/assets/fish-BvzEVeQv.js +1 -0
  96. package/dist/assets/fluent-C4IJs8-o.js +1 -0
  97. package/dist/assets/forth-Ffai-XNe.js +1 -0
  98. package/dist/assets/fortran-DYz_wnZ1.js +1 -0
  99. package/dist/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
  100. package/dist/assets/fortran-free-form-D22FLkUw.js +1 -0
  101. package/dist/assets/fsharp-CXgrBDvD.js +1 -0
  102. package/dist/assets/gas-Bneqetm1.js +1 -0
  103. package/dist/assets/gdresource-B7Tvp0Sc.js +1 -0
  104. package/dist/assets/gdscript-DTMYz4Jt.js +1 -0
  105. package/dist/assets/gdshader-DkwncUOv.js +1 -0
  106. package/dist/assets/genie-D0YGMca9.js +1 -0
  107. package/dist/assets/gherkin-DyxjwDmM.js +1 -0
  108. package/dist/assets/gherkin-heZmZLOM.js +1 -0
  109. package/dist/assets/git-commit-F4YmCXRG.js +1 -0
  110. package/dist/assets/git-rebase-r7XF79zn.js +1 -0
  111. package/dist/assets/github-dark-DHJKELXO.js +1 -0
  112. package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
  113. package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  114. package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  115. package/dist/assets/github-light-DAi9KRSo.js +1 -0
  116. package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
  117. package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  118. package/dist/assets/gleam-BspZqrRM.js +1 -0
  119. package/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
  120. package/dist/assets/glimmer-ts-U6CK756n.js +1 -0
  121. package/dist/assets/glsl-DplSGwfg.js +1 -0
  122. package/dist/assets/gnuplot-DdkO51Og.js +1 -0
  123. package/dist/assets/go-Dn2_MT6a.js +1 -0
  124. package/dist/assets/graphql-ChdNCCLP.js +1 -0
  125. package/dist/assets/groovy-D9Dt4D0W.js +1 -0
  126. package/dist/assets/groovy-gcz8RCvz.js +1 -0
  127. package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  128. package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  129. package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  130. package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  131. package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  132. package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  133. package/dist/assets/hack-CaT9iCJl.js +1 -0
  134. package/dist/assets/haml-B8DHNrY2.js +1 -0
  135. package/dist/assets/handlebars-BL8al0AC.js +1 -0
  136. package/dist/assets/haskell-Cw1EW3IL.js +1 -0
  137. package/dist/assets/haskell-Df6bDoY_.js +1 -0
  138. package/dist/assets/haxe-CzTSHFRz.js +1 -0
  139. package/dist/assets/haxe-H-WmDvRZ.js +1 -0
  140. package/dist/assets/hcl-BWvSN4gD.js +1 -0
  141. package/dist/assets/hjson-D5-asLiD.js +1 -0
  142. package/dist/assets/hlsl-D3lLCCz7.js +1 -0
  143. package/dist/assets/houston-DnULxvSX.js +1 -0
  144. package/dist/assets/html-GMplVEZG.js +1 -0
  145. package/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
  146. package/dist/assets/http-DBlCnlav.js +1 -0
  147. package/dist/assets/http-jrhK8wxY.js +1 -0
  148. package/dist/assets/hurl-irOxFIW8.js +1 -0
  149. package/dist/assets/hxml-Bvhsp5Yf.js +1 -0
  150. package/dist/assets/hy-DFXneXwc.js +1 -0
  151. package/dist/assets/idl-BEugSyMb.js +1 -0
  152. package/dist/assets/imba-DGztddWO.js +1 -0
  153. package/dist/assets/index-8c6bEJ99.js +1 -0
  154. package/dist/assets/index-AbWe21oh.js +2 -0
  155. package/dist/assets/index-ArhptQw0.js +1 -0
  156. package/dist/assets/index-B1hpa--1.js +3 -0
  157. package/dist/assets/index-Bafja8o4.js +1 -0
  158. package/dist/assets/index-Bp00uZNc.js +1 -0
  159. package/dist/assets/index-BsTieXqQ.js +1 -0
  160. package/dist/assets/index-BvGAWAqS.js +1 -0
  161. package/dist/assets/index-CCfVkFzN.js +1 -0
  162. package/dist/assets/index-D-Urq2hl.css +1 -0
  163. package/dist/assets/index-D3mXuuih.js +1 -0
  164. package/dist/assets/index-DFOLYN6W.js +1 -0
  165. package/dist/assets/index-DpxkOmNJ.js +7 -0
  166. package/dist/assets/index-YZ-iXB95.js +309 -0
  167. package/dist/assets/index-eA_XNQ_L.js +1 -0
  168. package/dist/assets/index-ftYom_wU.js +1 -0
  169. package/dist/assets/index-gvPT4BlL.js +1 -0
  170. package/dist/assets/ini-BEwlwnbL.js +1 -0
  171. package/dist/assets/java-CylS5w8V.js +1 -0
  172. package/dist/assets/javascript-iXu5QeM3.js +1 -0
  173. package/dist/assets/javascript-wDzz0qaB.js +1 -0
  174. package/dist/assets/jinja-4LBKfQ-Z.js +1 -0
  175. package/dist/assets/jison-wvAkD_A8.js +1 -0
  176. package/dist/assets/json-Cp-IABpG.js +1 -0
  177. package/dist/assets/json5-C9tS-k6U.js +1 -0
  178. package/dist/assets/jsonc-Des-eS-w.js +1 -0
  179. package/dist/assets/jsonl-DcaNXYhu.js +1 -0
  180. package/dist/assets/jsonnet-DFQXde-d.js +1 -0
  181. package/dist/assets/jssm-C2t-YnRu.js +1 -0
  182. package/dist/assets/jsx-g9-lgVsj.js +1 -0
  183. package/dist/assets/julia-C8NyazO9.js +1 -0
  184. package/dist/assets/julia-DuME0IfC.js +1 -0
  185. package/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  186. package/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  187. package/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
  188. package/dist/assets/kdl-DV7GczEv.js +1 -0
  189. package/dist/assets/kotlin-BdnUsdx6.js +1 -0
  190. package/dist/assets/kusto-BvAqAH-y.js +1 -0
  191. package/dist/assets/laserwave-DUszq2jm.js +1 -0
  192. package/dist/assets/latex-BdAV_C_H.js +1 -0
  193. package/dist/assets/lean-Bc6EcWN3.js +1 -0
  194. package/dist/assets/less-B1dDrJ26.js +1 -0
  195. package/dist/assets/light-plus-B7mTdjB0.js +1 -0
  196. package/dist/assets/liquid-DYVedYrR.js +1 -0
  197. package/dist/assets/livescript-BwQOo05w.js +1 -0
  198. package/dist/assets/llvm-BtvRca6l.js +1 -0
  199. package/dist/assets/log-2UxHyX5q.js +1 -0
  200. package/dist/assets/logo-BtOb2qkB.js +1 -0
  201. package/dist/assets/lua-BbnMAYS6.js +1 -0
  202. package/dist/assets/lua-BgMRiT3U.js +1 -0
  203. package/dist/assets/luau-CXu1NL6O.js +1 -0
  204. package/dist/assets/make-CHLpvVh8.js +1 -0
  205. package/dist/assets/markdown-Cvjx9yec.js +1 -0
  206. package/dist/assets/marko-CPi9NSCl.js +1 -0
  207. package/dist/assets/material-theme-D5KoaKCx.js +1 -0
  208. package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
  209. package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  210. package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
  211. package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  212. package/dist/assets/mathematica-DTrFuWx2.js +1 -0
  213. package/dist/assets/matlab-D7o27uSR.js +1 -0
  214. package/dist/assets/mbox-CNhZ1qSd.js +1 -0
  215. package/dist/assets/mdc-DUICxH0z.js +1 -0
  216. package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  217. package/dist/assets/mermaid-DKYwYmdq.js +1 -0
  218. package/dist/assets/min-dark-CafNBF8u.js +1 -0
  219. package/dist/assets/min-light-CTRr51gU.js +1 -0
  220. package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
  221. package/dist/assets/mirc-CjQqDB4T.js +1 -0
  222. package/dist/assets/mllike-CXdrOF99.js +1 -0
  223. package/dist/assets/modelica-Dc1JOy9r.js +1 -0
  224. package/dist/assets/mojo-1DNp92w6.js +1 -0
  225. package/dist/assets/monokai-D4h5O-jR.js +1 -0
  226. package/dist/assets/move-Bu9oaDYs.js +1 -0
  227. package/dist/assets/mscgen-BA5vi2Kp.js +1 -0
  228. package/dist/assets/mumps-BT43cFF4.js +1 -0
  229. package/dist/assets/narrat-DRg8JJMk.js +1 -0
  230. package/dist/assets/nextflow-BrzmwbiE.js +1 -0
  231. package/dist/assets/nginx-DdIZxoE0.js +1 -0
  232. package/dist/assets/nginx-DknmC5AR.js +1 -0
  233. package/dist/assets/night-owl-C39BiMTA.js +1 -0
  234. package/dist/assets/nim-CVrawwO9.js +1 -0
  235. package/dist/assets/nix-c8nO5XWb.js +1 -0
  236. package/dist/assets/nord-Ddv68eIx.js +1 -0
  237. package/dist/assets/nsis-LdVXkNf5.js +1 -0
  238. package/dist/assets/ntriples-BfvgReVJ.js +1 -0
  239. package/dist/assets/nushell-C-sUppwS.js +1 -0
  240. package/dist/assets/objective-c-DXmwc3jG.js +1 -0
  241. package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
  242. package/dist/assets/ocaml-C0hk2d4L.js +1 -0
  243. package/dist/assets/octave-Ck1zUtKM.js +1 -0
  244. package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  245. package/dist/assets/one-light-PoHY5YXO.js +1 -0
  246. package/dist/assets/openscad-C4EeE6gA.js +1 -0
  247. package/dist/assets/oz-BzwKVEFT.js +1 -0
  248. package/dist/assets/pascal--L3eBynH.js +1 -0
  249. package/dist/assets/pascal-D93ZcfNL.js +1 -0
  250. package/dist/assets/perl-C0TMdlhV.js +1 -0
  251. package/dist/assets/perl-CdXCOZ3F.js +1 -0
  252. package/dist/assets/php-CDn_0X-4.js +1 -0
  253. package/dist/assets/pig-CevX1Tat.js +1 -0
  254. package/dist/assets/pkl-u5AG7uiY.js +1 -0
  255. package/dist/assets/plastic-3e1v2bzS.js +1 -0
  256. package/dist/assets/plsql-ChMvpjG-.js +1 -0
  257. package/dist/assets/po-BTJTHyun.js +1 -0
  258. package/dist/assets/poimandres-CS3Unz2-.js +1 -0
  259. package/dist/assets/polar-C0HS_06l.js +1 -0
  260. package/dist/assets/postcss-CXtECtnM.js +1 -0
  261. package/dist/assets/powerquery-CEu0bR-o.js +1 -0
  262. package/dist/assets/powershell-CFHJl5sT.js +1 -0
  263. package/dist/assets/powershell-Dpen1YoG.js +1 -0
  264. package/dist/assets/prisma-Dd19v3D-.js +1 -0
  265. package/dist/assets/prolog-CbFg5uaA.js +1 -0
  266. package/dist/assets/properties-C78fOPTZ.js +1 -0
  267. package/dist/assets/proto-DyJlTyXw.js +1 -0
  268. package/dist/assets/protobuf-ChK-085T.js +1 -0
  269. package/dist/assets/pug-CGlum2m_.js +1 -0
  270. package/dist/assets/pug-DeIclll2.js +1 -0
  271. package/dist/assets/puppet-BMWR74SV.js +1 -0
  272. package/dist/assets/puppet-DMA9R1ak.js +1 -0
  273. package/dist/assets/purescript-CklMAg4u.js +1 -0
  274. package/dist/assets/python-B6aJPvgy.js +1 -0
  275. package/dist/assets/python-BuPzkPfP.js +1 -0
  276. package/dist/assets/q-pXgVlZs6.js +1 -0
  277. package/dist/assets/qml-3beO22l8.js +1 -0
  278. package/dist/assets/qmldir-C8lEn-DE.js +1 -0
  279. package/dist/assets/qss-IeuSbFQv.js +1 -0
  280. package/dist/assets/r-B6wPVr8A.js +1 -0
  281. package/dist/assets/r-DiinP2Uv.js +1 -0
  282. package/dist/assets/racket-BqYA7rlc.js +1 -0
  283. package/dist/assets/raku-DXvB9xmW.js +1 -0
  284. package/dist/assets/razor-CE9lU5zL.js +1 -0
  285. package/dist/assets/red-bN70gL4F.js +1 -0
  286. package/dist/assets/reg-C-SQnVFl.js +1 -0
  287. package/dist/assets/regexp-CDVJQ6XC.js +1 -0
  288. package/dist/assets/rel-C3B-1QV4.js +1 -0
  289. package/dist/assets/riscv-BM1_JUlF.js +1 -0
  290. package/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  291. package/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  292. package/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
  293. package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
  294. package/dist/assets/rpm-CTu-6PCP.js +1 -0
  295. package/dist/assets/rst-B0xPkSld.js +1 -0
  296. package/dist/assets/ruby-B2Rjki9n.js +1 -0
  297. package/dist/assets/ruby-BvKwtOVI.js +1 -0
  298. package/dist/assets/rust-B1yitclQ.js +1 -0
  299. package/dist/assets/sas-B4kiWyti.js +1 -0
  300. package/dist/assets/sas-cz2c8ADy.js +1 -0
  301. package/dist/assets/sass-Cj5Yp3dK.js +1 -0
  302. package/dist/assets/scala-C151Ov-r.js +1 -0
  303. package/dist/assets/scheme-C41bIUwD.js +1 -0
  304. package/dist/assets/scheme-C98Dy4si.js +1 -0
  305. package/dist/assets/scss-OYdSNvt2.js +1 -0
  306. package/dist/assets/sdbl-DVxCFoDh.js +1 -0
  307. package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
  308. package/dist/assets/shell-CjFT_Tl9.js +1 -0
  309. package/dist/assets/shellscript-Yzrsuije.js +1 -0
  310. package/dist/assets/shellsession-BADoaaVG.js +1 -0
  311. package/dist/assets/sieve-C3Gn_uJK.js +1 -0
  312. package/dist/assets/simple-mode-GW_nhZxv.js +1 -0
  313. package/dist/assets/slack-dark-BthQWCQV.js +1 -0
  314. package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
  315. package/dist/assets/smalltalk-BERRCDM3.js +1 -0
  316. package/dist/assets/smalltalk-CnHTOXQT.js +1 -0
  317. package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
  318. package/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
  319. package/dist/assets/solarized-light-L9t79GZl.js +1 -0
  320. package/dist/assets/solidity-rGO070M0.js +1 -0
  321. package/dist/assets/solr-DehyRSwq.js +1 -0
  322. package/dist/assets/soy-Brmx7dQM.js +1 -0
  323. package/dist/assets/sparql-DkYu6x3z.js +1 -0
  324. package/dist/assets/sparql-rVzFXLq3.js +1 -0
  325. package/dist/assets/splunk-BtCnVYZw.js +1 -0
  326. package/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  327. package/dist/assets/sql-BLtJtn59.js +1 -0
  328. package/dist/assets/sql-D0XecflT.js +1 -0
  329. package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
  330. package/dist/assets/stata-BH5u7GGu.js +1 -0
  331. package/dist/assets/stex-C3f8Ysf7.js +1 -0
  332. package/dist/assets/stylus-B533Al4x.js +1 -0
  333. package/dist/assets/stylus-BEDo0Tqx.js +1 -0
  334. package/dist/assets/svelte-3Dk4HxPD.js +1 -0
  335. package/dist/assets/swift-BzpIVaGY.js +1 -0
  336. package/dist/assets/swift-Dg5xB15N.js +1 -0
  337. package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
  338. package/dist/assets/system-verilog-CnnmHF94.js +1 -0
  339. package/dist/assets/systemd-4A_iFExJ.js +1 -0
  340. package/dist/assets/talonscript-CkByrt1z.js +1 -0
  341. package/dist/assets/tasl-QIJgUcNo.js +1 -0
  342. package/dist/assets/tcl-DVfN8rqt.js +1 -0
  343. package/dist/assets/tcl-dwOrl1Do.js +1 -0
  344. package/dist/assets/templ-W15q3VgB.js +1 -0
  345. package/dist/assets/terraform-BETggiCN.js +1 -0
  346. package/dist/assets/tex-CxkMU7Pf.js +1 -0
  347. package/dist/assets/textile-CnDTJFAw.js +1 -0
  348. package/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  349. package/dist/assets/tiki-DGYXhP31.js +1 -0
  350. package/dist/assets/tokyo-night-hegEt444.js +1 -0
  351. package/dist/assets/toml-Bm5Em-hy.js +1 -0
  352. package/dist/assets/toml-vGWfd6FD.js +1 -0
  353. package/dist/assets/troff-wAsdV37c.js +1 -0
  354. package/dist/assets/ts-tags-zn1MmPIZ.js +1 -0
  355. package/dist/assets/tsv-B_m7g4N7.js +1 -0
  356. package/dist/assets/tsx-COt5Ahok.js +1 -0
  357. package/dist/assets/ttcn-CfJYG6tj.js +1 -0
  358. package/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  359. package/dist/assets/turtle-B1tBg_DP.js +1 -0
  360. package/dist/assets/turtle-BsS91CYL.js +1 -0
  361. package/dist/assets/twig-CO9l9SDP.js +1 -0
  362. package/dist/assets/typescript-BPQ3VLAy.js +1 -0
  363. package/dist/assets/typespec-BGHnOYBU.js +1 -0
  364. package/dist/assets/typst-DHCkPAjA.js +1 -0
  365. package/dist/assets/v-BcVCzyr7.js +1 -0
  366. package/dist/assets/vala-CsfeWuGM.js +1 -0
  367. package/dist/assets/vb-CmGdzxic.js +1 -0
  368. package/dist/assets/vb-D17OF-Vu.js +1 -0
  369. package/dist/assets/vbscript-BuJXcnF6.js +1 -0
  370. package/dist/assets/velocity-D8B20fx6.js +1 -0
  371. package/dist/assets/verilog-BQ8w6xss.js +1 -0
  372. package/dist/assets/verilog-C6RDOZhf.js +1 -0
  373. package/dist/assets/vesper-DU1UobuO.js +1 -0
  374. package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
  375. package/dist/assets/vhdl-lSbBsy5d.js +1 -0
  376. package/dist/assets/viml-CJc9bBzg.js +1 -0
  377. package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
  378. package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
  379. package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
  380. package/dist/assets/vue-DnHKYNfI.js +1 -0
  381. package/dist/assets/vue-html-CChd_i61.js +1 -0
  382. package/dist/assets/vue-vine-8moa0y9V.js +1 -0
  383. package/dist/assets/vyper-CDx5xZoG.js +1 -0
  384. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  385. package/dist/assets/wasm-MzD3tlZU.js +1 -0
  386. package/dist/assets/webidl-ZXfAyPTL.js +1 -0
  387. package/dist/assets/wenyan-BV7otONQ.js +1 -0
  388. package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
  389. package/dist/assets/wikitext-BhOHFoWU.js +1 -0
  390. package/dist/assets/wit-5i3qLPDT.js +1 -0
  391. package/dist/assets/wolfram-lXgVvXCa.js +1 -0
  392. package/dist/assets/xml-sdJ4AIDG.js +1 -0
  393. package/dist/assets/xquery-DzFWVndE.js +1 -0
  394. package/dist/assets/xsl-CtQFsRM5.js +1 -0
  395. package/dist/assets/yacas-BJ4BC0dw.js +1 -0
  396. package/dist/assets/yaml-Buea-lGh.js +1 -0
  397. package/dist/assets/z80-Hz9HOZM7.js +1 -0
  398. package/dist/assets/zenscript-DVFEvuxE.js +1 -0
  399. package/dist/assets/zig-VOosw3JB.js +1 -0
  400. package/dist/index.html +47 -0
  401. package/dist/logo.svg +8 -0
  402. package/dist/openspec_pixel_dark.svg +89 -0
  403. package/dist/openspec_pixel_light.svg +89 -0
  404. package/index.html +46 -0
  405. package/package.json +66 -0
  406. package/src/App.tsx +124 -0
  407. package/src/components/StaticModeBanner.tsx +46 -0
  408. package/src/components/change-overview.tsx +156 -0
  409. package/src/components/cli-terminal.tsx +93 -0
  410. package/src/components/code-editor.tsx +232 -0
  411. package/src/components/copyable-path.tsx +44 -0
  412. package/src/components/dialog.tsx +193 -0
  413. package/src/components/folder-editor-viewer.tsx +411 -0
  414. package/src/components/global-archive-modal.tsx +205 -0
  415. package/src/components/layout/desktop-sidebar.tsx +48 -0
  416. package/src/components/layout/index.ts +6 -0
  417. package/src/components/layout/mobile-header.tsx +91 -0
  418. package/src/components/layout/mobile-tabbar.tsx +20 -0
  419. package/src/components/layout/nav-items.ts +33 -0
  420. package/src/components/layout/root-layout.tsx +67 -0
  421. package/src/components/layout/status-bar.tsx +69 -0
  422. package/src/components/markdown-content.tsx +104 -0
  423. package/src/components/markdown-viewer.tsx +415 -0
  424. package/src/components/path-marquee.tsx +104 -0
  425. package/src/components/tabs.tsx +151 -0
  426. package/src/components/tasks-view.test.tsx +45 -0
  427. package/src/components/tasks-view.tsx +209 -0
  428. package/src/components/toc-context.tsx +177 -0
  429. package/src/components/toc.tsx +290 -0
  430. package/src/entry-client.tsx +47 -0
  431. package/src/index.css +481 -0
  432. package/src/lib/api-config.ts +72 -0
  433. package/src/lib/archive-modal-context.tsx +45 -0
  434. package/src/lib/codemirror-markdown-preview.ts +494 -0
  435. package/src/lib/format-time.ts +51 -0
  436. package/src/lib/shiki-highlighter.ts +47 -0
  437. package/src/lib/static-data-provider.ts +386 -0
  438. package/src/lib/static-mode.ts +158 -0
  439. package/src/lib/trpc.ts +107 -0
  440. package/src/lib/use-cli-runner.tsx +433 -0
  441. package/src/lib/use-dark-mode.ts +28 -0
  442. package/src/lib/use-server-status.ts +208 -0
  443. package/src/lib/use-subscription.ts +375 -0
  444. package/src/lib/use-tabs-status-by-query.ts +78 -0
  445. package/src/main.tsx +9 -0
  446. package/src/routes/archive-list.tsx +65 -0
  447. package/src/routes/archive-view.tsx +116 -0
  448. package/src/routes/change-list.tsx +63 -0
  449. package/src/routes/change-view.tsx +188 -0
  450. package/src/routes/dashboard.tsx +204 -0
  451. package/src/routes/project.tsx +272 -0
  452. package/src/routes/settings.tsx +816 -0
  453. package/src/routes/spec-list.tsx +49 -0
  454. package/src/routes/spec-view.tsx +164 -0
  455. package/src/ssg/entry-server.tsx +111 -0
  456. package/src/ssg/index.ts +6 -0
  457. package/src/ssg/prerender.ts +111 -0
  458. package/src/ssg/static-data-context.tsx +49 -0
  459. package/src/ssg/types.ts +5 -0
  460. package/src/test/setup.ts +1 -0
  461. package/src/vite-env.d.ts +6 -0
  462. package/tsconfig.json +18 -0
  463. package/vite.config.ts +58 -0
@@ -0,0 +1,433 @@
1
+ import { Check, Loader2, Sparkles, XCircle } from 'lucide-react'
2
+ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
3
+ import { isStaticMode } from './static-mode'
4
+ import { trpcClient } from './trpc'
5
+
6
+ export type CommandRunStatus = 'idle' | 'loading' | 'running' | 'success' | 'error'
7
+ export type OverallStatus = 'idle' | 'running' | 'success' | 'error'
8
+ export type CliRunnerLine =
9
+ | { id: string; kind: 'ascii'; text: string; tone?: 'success' | 'error' | 'info' }
10
+ | { id: string; kind: 'html'; node: ReactNode }
11
+
12
+ export interface CommandDescriptor {
13
+ id: string
14
+ command: string
15
+ args: string[]
16
+ status: CommandRunStatus
17
+ exitCode: number | null
18
+ }
19
+
20
+ export type ProcessEvent = 'data' | 'error' | 'close' | 'exit'
21
+
22
+ export interface CommandProcess {
23
+ id: string
24
+ command: string
25
+ args: string[]
26
+ status: CommandRunStatus
27
+ exitCode: number | null
28
+ done: Promise<number | null>
29
+ on: (event: ProcessEvent, handler: (payload: string | number | null) => void) => () => void
30
+ cancel: () => void
31
+ }
32
+
33
+ interface CommandInput {
34
+ command: string
35
+ args?: string[]
36
+ }
37
+
38
+ interface UseCliRunnerOptions {
39
+ onCreateProcess?: (process: CommandProcess) => void
40
+ }
41
+
42
+ interface LogLine {
43
+ id: string
44
+ commandId: string
45
+ text: string
46
+ tone?: 'success' | 'error' | 'info'
47
+ kind?: 'stdout' | 'stderr' | 'meta'
48
+ }
49
+
50
+ function createId() {
51
+ return typeof crypto !== 'undefined' && crypto.randomUUID
52
+ ? crypto.randomUUID()
53
+ : Math.random().toString(36).slice(2)
54
+ }
55
+
56
+ function asCommandText(command: string, args: string[]) {
57
+ const argText = args.length > 0 ? ` ${args.join(' ')}` : ''
58
+ return `${command}${argText}`
59
+ }
60
+
61
+ function deriveOverallStatus(commands: CommandDescriptor[]): OverallStatus {
62
+ if (commands.some((c) => c.status === 'loading' || c.status === 'running')) return 'running'
63
+ if (commands.some((c) => c.status === 'error')) return 'error'
64
+ if (commands.length > 0 && commands.every((c) => c.status === 'success')) return 'success'
65
+ return 'idle'
66
+ }
67
+
68
+ export function useCliRunner(options: UseCliRunnerOptions = {}) {
69
+ const [commands, setCommands] = useState<CommandDescriptor[]>([])
70
+ const [logs, setLogs] = useState<LogLine[]>([])
71
+ const [lastExitCode, setLastExitCode] = useState<number | null>(null)
72
+
73
+ const activeSubscriptionRef = useRef<{ unsubscribe: () => void } | null>(null)
74
+ const activeCommandIdRef = useRef<string | null>(null)
75
+ const commandStateRef = useRef<CommandDescriptor[]>([])
76
+ const optionsRef = useRef<UseCliRunnerOptions>(options)
77
+
78
+ useEffect(() => {
79
+ optionsRef.current = options
80
+ }, [options])
81
+
82
+ const updateCommands = useCallback(
83
+ (updater: (prev: CommandDescriptor[]) => CommandDescriptor[]) => {
84
+ setCommands((prev) => {
85
+ const next = updater(prev)
86
+ commandStateRef.current = next
87
+ return next
88
+ })
89
+ },
90
+ []
91
+ )
92
+
93
+ // keep ref in sync for runAll loop
94
+ useEffect(() => {
95
+ commandStateRef.current = commands
96
+ }, [commands])
97
+
98
+ const reset = useCallback(() => {
99
+ activeSubscriptionRef.current?.unsubscribe?.()
100
+ activeSubscriptionRef.current = null
101
+ activeCommandIdRef.current = null
102
+ commandStateRef.current = []
103
+ setCommands([])
104
+ setLogs([])
105
+ setLastExitCode(null)
106
+ }, [])
107
+
108
+ const cancel = useCallback(() => {
109
+ activeSubscriptionRef.current?.unsubscribe?.()
110
+ activeSubscriptionRef.current = null
111
+ const activeId = activeCommandIdRef.current
112
+ if (activeId) {
113
+ updateCommands((prev) =>
114
+ prev.map((c) => (c.id === activeId ? { ...c, status: 'error', exitCode: null } : c))
115
+ )
116
+ setLogs((prev) => [
117
+ ...prev,
118
+ {
119
+ id: createId(),
120
+ commandId: activeId,
121
+ kind: 'meta',
122
+ text: 'Process cancelled by user',
123
+ tone: 'error',
124
+ },
125
+ ])
126
+ }
127
+ activeCommandIdRef.current = null
128
+ }, [updateCommands])
129
+
130
+ const add = useCallback((command: string, args: string[] = [], at = -1) => {
131
+ const id = createId()
132
+ updateCommands((prev) => {
133
+ const next = [...prev]
134
+ const descriptor: CommandDescriptor = {
135
+ id,
136
+ command,
137
+ args,
138
+ status: 'idle',
139
+ exitCode: null,
140
+ }
141
+ if (at >= 0 && at < next.length) {
142
+ next.splice(at, 0, descriptor)
143
+ } else {
144
+ next.push(descriptor)
145
+ }
146
+ return next
147
+ })
148
+ return id
149
+ }, [])
150
+
151
+ const remove = useCallback(
152
+ (id: string) => {
153
+ updateCommands((prev) => prev.filter((c) => c.id !== id))
154
+ setLogs((prev) => prev.filter((log) => log.commandId !== id))
155
+ if (activeCommandIdRef.current === id) {
156
+ cancel()
157
+ }
158
+ },
159
+ [cancel, updateCommands]
160
+ )
161
+
162
+ const replaceAll = useCallback(
163
+ (items: CommandInput[]) => {
164
+ activeSubscriptionRef.current?.unsubscribe?.()
165
+ activeSubscriptionRef.current = null
166
+ activeCommandIdRef.current = null
167
+ setLogs([])
168
+ setLastExitCode(null)
169
+ updateCommands(() =>
170
+ items.map((item) => ({
171
+ id: createId(),
172
+ command: item.command,
173
+ args: item.args ?? [],
174
+ status: 'idle',
175
+ exitCode: null,
176
+ }))
177
+ )
178
+ },
179
+ [updateCommands]
180
+ )
181
+
182
+ const list = useCallback(() => [...commandStateRef.current], [])
183
+
184
+ const appendLog = useCallback((log: LogLine) => {
185
+ setLogs((prev) => [...prev, log])
186
+ }, [])
187
+
188
+ const run = useCallback(
189
+ async (id?: string): Promise<CommandProcess | null> => {
190
+ // Don't run CLI commands in static mode
191
+ if (isStaticMode()) {
192
+ console.warn('CLI runner is disabled in static mode')
193
+ return null
194
+ }
195
+
196
+ const targetId = id ?? commandStateRef.current.find((c) => c.status === 'idle')?.id
197
+ const target = commandStateRef.current.find((c) => c.id === targetId)
198
+ if (!target) return null
199
+
200
+ activeSubscriptionRef.current?.unsubscribe?.()
201
+ activeCommandIdRef.current = target.id
202
+
203
+ updateCommands((prev) =>
204
+ prev.map((c) => (c.id === target.id ? { ...c, status: 'loading', exitCode: null } : c))
205
+ )
206
+
207
+ let resolveDone: (code: number | null) => void = () => {}
208
+ const done = new Promise<number | null>((resolve) => {
209
+ resolveDone = resolve
210
+ })
211
+
212
+ const listeners: Record<ProcessEvent, Set<(payload: string | number | null) => void>> = {
213
+ data: new Set(),
214
+ error: new Set(),
215
+ close: new Set(),
216
+ exit: new Set(),
217
+ }
218
+
219
+ const emit = (event: ProcessEvent, payload: string | number | null) => {
220
+ listeners[event].forEach((handler) => handler(payload))
221
+ }
222
+
223
+ const process: CommandProcess = {
224
+ id: target.id,
225
+ command: target.command,
226
+ args: target.args,
227
+ status: 'loading',
228
+ exitCode: null,
229
+ done,
230
+ on: (event, handler) => {
231
+ listeners[event].add(handler)
232
+ return () => listeners[event].delete(handler)
233
+ },
234
+ cancel: () => {
235
+ cancel()
236
+ },
237
+ }
238
+
239
+ optionsRef.current.onCreateProcess?.(process)
240
+
241
+ const subscription = trpcClient.cli.runCommandStream.subscribe(
242
+ { command: target.command, args: target.args },
243
+ {
244
+ onData: (event) => {
245
+ if (event.type === 'command') {
246
+ updateCommands((prev) =>
247
+ prev.map((c) => (c.id === target.id ? { ...c, status: 'running' } : c))
248
+ )
249
+ process.status = 'running'
250
+ emit('data', event.data ?? '')
251
+ return
252
+ }
253
+
254
+ if (event.type === 'stdout' && event.data) {
255
+ appendLog({
256
+ id: createId(),
257
+ commandId: target.id,
258
+ kind: 'stdout',
259
+ text: event.data,
260
+ })
261
+ emit('data', event.data)
262
+ return
263
+ }
264
+
265
+ if (event.type === 'stderr' && event.data) {
266
+ appendLog({
267
+ id: createId(),
268
+ commandId: target.id,
269
+ kind: 'stderr',
270
+ text: event.data,
271
+ tone: 'error',
272
+ })
273
+ emit('error', event.data)
274
+ return
275
+ }
276
+
277
+ if (event.type === 'exit') {
278
+ const exitCode = event.exitCode ?? null
279
+ setLastExitCode(exitCode)
280
+ const status: CommandRunStatus = exitCode === 0 ? 'success' : 'error'
281
+ updateCommands((prev) =>
282
+ prev.map((c) => (c.id === target.id ? { ...c, status, exitCode } : c))
283
+ )
284
+ process.status = status
285
+ process.exitCode = exitCode
286
+ appendLog({
287
+ id: createId(),
288
+ commandId: target.id,
289
+ kind: 'meta',
290
+ text: `Process exited with code ${exitCode ?? 'unknown'}`,
291
+ tone: exitCode === 0 ? 'success' : 'error',
292
+ })
293
+ emit('close', exitCode)
294
+ emit('exit', exitCode)
295
+ resolveDone(exitCode)
296
+ activeSubscriptionRef.current?.unsubscribe?.()
297
+ activeSubscriptionRef.current = null
298
+ activeCommandIdRef.current = null
299
+ }
300
+ },
301
+ onError: (err) => {
302
+ const message = err instanceof Error ? err.message : String(err)
303
+ updateCommands((prev) =>
304
+ prev.map((c) => (c.id === target.id ? { ...c, status: 'error', exitCode: null } : c))
305
+ )
306
+ process.status = 'error'
307
+ appendLog({
308
+ id: createId(),
309
+ commandId: target.id,
310
+ kind: 'meta',
311
+ text: `Error: ${message}`,
312
+ tone: 'error',
313
+ })
314
+ emit('error', message)
315
+ resolveDone(null)
316
+ activeSubscriptionRef.current?.unsubscribe?.()
317
+ activeSubscriptionRef.current = null
318
+ activeCommandIdRef.current = null
319
+ },
320
+ }
321
+ )
322
+
323
+ process.status = 'running'
324
+ activeSubscriptionRef.current = subscription
325
+
326
+ return process
327
+ },
328
+ [appendLog, cancel, updateCommands]
329
+ )
330
+
331
+ const runAll = useCallback(async () => {
332
+ let next = commandStateRef.current.find((c) => c.status === 'idle')
333
+ while (next) {
334
+ const process = await run(next.id)
335
+ if (!process) break
336
+ const exitCode = await process.done
337
+ if (exitCode !== 0) break
338
+ next = commandStateRef.current.find((c) => c.status === 'idle')
339
+ }
340
+ }, [run])
341
+
342
+ useEffect(() => {
343
+ return () => {
344
+ cancel()
345
+ }
346
+ }, [cancel])
347
+
348
+ const lines: CliRunnerLine[] = useMemo(
349
+ () =>
350
+ commands.flatMap((cmd) => {
351
+ const statusIcon =
352
+ cmd.status === 'loading' ? (
353
+ <Loader2 className="h-3 w-3 animate-spin text-sky-300" />
354
+ ) : cmd.status === 'running' ? (
355
+ <div className="flex items-center gap-1 text-amber-200">
356
+ <Sparkles className="h-3 w-3 animate-pulse" />
357
+ <Loader2 className="h-3 w-3 animate-spin" />
358
+ </div>
359
+ ) : cmd.status === 'success' ? (
360
+ <Check className="h-3 w-3 text-green-400" />
361
+ ) : cmd.status === 'error' ? (
362
+ <XCircle className="h-3 w-3 text-red-400" />
363
+ ) : null
364
+
365
+ const headerText =
366
+ cmd.status === 'idle'
367
+ ? `# ${asCommandText(cmd.command, cmd.args)}`
368
+ : `$ ${asCommandText(cmd.command, cmd.args)}`
369
+
370
+ const header: CliRunnerLine[] = [
371
+ {
372
+ id: `cmd-${cmd.id}`,
373
+ kind: 'html',
374
+ node: (
375
+ <div className="flex items-center gap-2">
376
+ <span
377
+ className={
378
+ cmd.status === 'running'
379
+ ? 'terminal-shimmer'
380
+ : cmd.status === 'loading'
381
+ ? 'animate-pulse text-blue-200'
382
+ : cmd.status === 'success'
383
+ ? 'text-green-300'
384
+ : cmd.status === 'error'
385
+ ? 'text-red-300'
386
+ : 'text-zinc-200'
387
+ }
388
+ >
389
+ {headerText}
390
+ </span>
391
+ {statusIcon}
392
+ </div>
393
+ ),
394
+ },
395
+ ]
396
+
397
+ const body: CliRunnerLine[] = logs
398
+ .filter((log) => log.commandId === cmd.id)
399
+ .map((log) => ({
400
+ id: log.id,
401
+ kind: 'ascii' as const,
402
+ text: log.text,
403
+ tone: log.tone,
404
+ }))
405
+
406
+ return [...header, ...body]
407
+ }),
408
+ [commands, logs]
409
+ )
410
+
411
+ const overallStatus = useMemo(() => deriveOverallStatus(commands), [commands])
412
+ const hasStarted = useMemo(() => commands.some((c) => c.status !== 'idle'), [commands])
413
+
414
+ return {
415
+ lines,
416
+ status: overallStatus,
417
+ exitCode: lastExitCode,
418
+ hasStarted,
419
+ commands: useMemo(
420
+ () => ({
421
+ add,
422
+ remove,
423
+ list,
424
+ replaceAll,
425
+ run,
426
+ runAll,
427
+ }),
428
+ [add, list, remove, replaceAll, run, runAll]
429
+ ),
430
+ reset,
431
+ cancel,
432
+ }
433
+ }
@@ -0,0 +1,28 @@
1
+ import { useSyncExternalStore } from 'react'
2
+
3
+ function subscribe(callback: () => void) {
4
+ const observer = new MutationObserver((mutations) => {
5
+ for (const mutation of mutations) {
6
+ if (mutation.attributeName === 'class') {
7
+ callback()
8
+ }
9
+ }
10
+ })
11
+
12
+ observer.observe(document.documentElement, { attributes: true })
13
+
14
+ return () => observer.disconnect()
15
+ }
16
+
17
+ function getSnapshot() {
18
+ return document.documentElement.classList.contains('dark')
19
+ }
20
+
21
+ function getServerSnapshot() {
22
+ return false
23
+ }
24
+
25
+ /** Hook to detect dark mode from document.documentElement.classList */
26
+ export function useDarkMode() {
27
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
28
+ }
@@ -0,0 +1,208 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
+ import { getHealthUrl } from './api-config'
3
+ import { isStaticMode } from './static-mode'
4
+ import { getWsClientInstance, WS_RETRY_DELAY_MS } from './trpc'
5
+
6
+ export interface ServerStatus {
7
+ connected: boolean
8
+ projectDir: string | null
9
+ dirName: string | null
10
+ watcherEnabled: boolean
11
+ error: string | null
12
+ /** WebSocket 连接状态 */
13
+ wsState: 'idle' | 'connecting' | 'pending'
14
+ /** 重连倒计时(秒),仅在 disconnected 时有值 */
15
+ reconnectCountdown: number | null
16
+ }
17
+
18
+ /**
19
+ * Hook to monitor server connection status and get project info
20
+ */
21
+ export function useServerStatus(): ServerStatus {
22
+ const [status, setStatus] = useState<ServerStatus>({
23
+ connected: false,
24
+ projectDir: null,
25
+ dirName: null,
26
+ watcherEnabled: false,
27
+ error: null,
28
+ wsState: 'idle',
29
+ reconnectCountdown: null,
30
+ })
31
+
32
+ // 用于追踪重连倒计时
33
+ const countdownRef = useRef<ReturnType<typeof setInterval> | null>(null)
34
+ const disconnectTimeRef = useRef<number | null>(null)
35
+
36
+ // 开始重连倒计时
37
+ const startReconnectCountdown = useCallback(() => {
38
+ disconnectTimeRef.current = Date.now()
39
+
40
+ // 清除之前的倒计时
41
+ if (countdownRef.current) {
42
+ clearInterval(countdownRef.current)
43
+ }
44
+
45
+ // 立即设置初始倒计时
46
+ setStatus((prev) => ({
47
+ ...prev,
48
+ reconnectCountdown: Math.ceil(WS_RETRY_DELAY_MS / 1000),
49
+ }))
50
+
51
+ // 每秒更新倒计时
52
+ countdownRef.current = setInterval(() => {
53
+ if (disconnectTimeRef.current === null) return
54
+
55
+ const elapsed = Date.now() - disconnectTimeRef.current
56
+ const remaining = Math.max(0, Math.ceil((WS_RETRY_DELAY_MS - elapsed) / 1000))
57
+
58
+ setStatus((prev) => ({
59
+ ...prev,
60
+ reconnectCountdown: remaining > 0 ? remaining : null,
61
+ }))
62
+
63
+ // 倒计时结束时清除 interval
64
+ if (remaining <= 0 && countdownRef.current) {
65
+ clearInterval(countdownRef.current)
66
+ countdownRef.current = null
67
+ disconnectTimeRef.current = null
68
+ }
69
+ }, 200)
70
+ }, [])
71
+
72
+ // 停止重连倒计时
73
+ const stopReconnectCountdown = useCallback(() => {
74
+ if (countdownRef.current) {
75
+ clearInterval(countdownRef.current)
76
+ countdownRef.current = null
77
+ }
78
+ disconnectTimeRef.current = null
79
+ setStatus((prev) => ({ ...prev, reconnectCountdown: null }))
80
+ }, [])
81
+
82
+ // 监听 WebSocket 连接状态
83
+ useEffect(() => {
84
+ // Skip WebSocket monitoring in static mode
85
+ if (isStaticMode()) {
86
+ return
87
+ }
88
+
89
+ const wsClient = getWsClientInstance()
90
+ if (!wsClient) {
91
+ return
92
+ }
93
+
94
+ // 订阅 wsClient 的连接状态
95
+ const subscription = wsClient.connectionState.subscribe({
96
+ next: (state) => {
97
+ setStatus((prev) => ({
98
+ ...prev,
99
+ wsState: state.state,
100
+ }))
101
+
102
+ // 当进入 connecting 状态且有 error 时,说明正在重连
103
+ if (state.state === 'connecting' && state.error) {
104
+ startReconnectCountdown()
105
+ } else {
106
+ stopReconnectCountdown()
107
+ }
108
+ },
109
+ })
110
+
111
+ return () => {
112
+ subscription.unsubscribe()
113
+ stopReconnectCountdown()
114
+ }
115
+ }, [startReconnectCountdown, stopReconnectCountdown])
116
+
117
+ // HTTP 健康检查逻辑
118
+ useEffect(() => {
119
+ // Skip health checks in static mode
120
+ if (isStaticMode()) {
121
+ setStatus((prev) => ({
122
+ ...prev,
123
+ connected: true,
124
+ projectDir: 'Static Export',
125
+ dirName: 'Static Export',
126
+ watcherEnabled: false,
127
+ error: null,
128
+ }))
129
+ document.title = 'OpenSpec UI (Static)'
130
+ return
131
+ }
132
+
133
+ let mounted = true
134
+ let retryTimeout: ReturnType<typeof setTimeout>
135
+
136
+ async function checkHealth() {
137
+ try {
138
+ const response = await fetch(getHealthUrl())
139
+ if (!response.ok) {
140
+ throw new Error(`HTTP ${response.status}`)
141
+ }
142
+ const data = await response.json()
143
+
144
+ if (mounted) {
145
+ const projectDir = data.projectDir as string
146
+ const dirName = projectDir.split('/').pop() || projectDir
147
+
148
+ setStatus((prev) => ({
149
+ ...prev,
150
+ connected: true,
151
+ projectDir,
152
+ dirName,
153
+ watcherEnabled: data.watcherEnabled,
154
+ error: null,
155
+ }))
156
+
157
+ // Update document title
158
+ document.title = `${dirName} - OpenSpec UI`
159
+ }
160
+ } catch (err) {
161
+ if (mounted) {
162
+ setStatus((prev) => ({
163
+ ...prev,
164
+ connected: false,
165
+ error: err instanceof Error ? err.message : 'Connection failed',
166
+ }))
167
+
168
+ // Reset title on disconnect
169
+ document.title = 'OpenSpec UI (Disconnected)'
170
+
171
+ // Retry after 3 seconds
172
+ retryTimeout = setTimeout(checkHealth, 3000)
173
+ }
174
+ }
175
+ }
176
+
177
+ checkHealth()
178
+
179
+ // Periodic health check every 30 seconds when connected
180
+ const interval = setInterval(() => {
181
+ if (mounted) {
182
+ checkHealth()
183
+ }
184
+ }, 30000)
185
+
186
+ return () => {
187
+ mounted = false
188
+ clearInterval(interval)
189
+ clearTimeout(retryTimeout)
190
+ }
191
+ }, [])
192
+
193
+ return status
194
+ }
195
+
196
+ /**
197
+ * 手动触发重连(通过重新创建 subscription 来间接触发)
198
+ * 注意:由于 trpc wsClient 的 API 限制,无法直接调用重连
199
+ * 这是一个 best-effort 的实现
200
+ */
201
+ export function useManualReconnect() {
202
+ // trpc wsClient 不暴露直接的重连 API
203
+ // 但可以通过 queryClient.invalidateQueries 触发重新订阅
204
+ return useCallback(() => {
205
+ // 刷新页面是最可靠的重连方式
206
+ window.location.reload()
207
+ }, [])
208
+ }