@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,188 @@
1
+ import { ChangeOverview } from '@/components/change-overview'
2
+ import { FolderEditorViewer } from '@/components/folder-editor-viewer'
3
+ import { Tabs, type Tab } from '@/components/tabs'
4
+ import { TasksView } from '@/components/tasks-view'
5
+ import { useArchiveModal } from '@/lib/archive-modal-context'
6
+ import { isStaticMode } from '@/lib/static-mode'
7
+ import { trpcClient } from '@/lib/trpc'
8
+ import { useChangeSubscription } from '@/lib/use-subscription'
9
+ import { useTabsStatusByQuery } from '@/lib/use-tabs-status-by-query'
10
+ import { useMutation } from '@tanstack/react-query'
11
+ import { Link, useNavigate, useParams } from '@tanstack/react-router'
12
+ import {
13
+ AlertCircle,
14
+ Archive,
15
+ ArrowLeft,
16
+ FileText,
17
+ FolderTree,
18
+ GitBranch,
19
+ ListChecks,
20
+ } from 'lucide-react'
21
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
22
+
23
+ export function ChangeView() {
24
+ const { changeId } = useParams({ from: '/changes/$changeId' })
25
+ const navigate = useNavigate()
26
+ const { openArchiveModal, state: archiveModalState } = useArchiveModal()
27
+ const { data: change, isLoading } = useChangeSubscription(changeId)
28
+ const [firstFrameLoading, setFirstFrameLoading] = useState(true)
29
+ useEffect(() => {
30
+ const id = requestAnimationFrame(() => setFirstFrameLoading(false))
31
+ return () => cancelAnimationFrame(id)
32
+ }, [])
33
+
34
+ // 保存最后一次有效的 changeName,用于在 change 被删除后打开 Modal
35
+ const lastChangeNameRef = useRef(change?.name ?? changeId)
36
+ if (change?.name) {
37
+ lastChangeNameRef.current = change.name
38
+ }
39
+
40
+ // 当 change 不存在且不在加载中且 Archive Modal 打开时,自动返回到 /changes
41
+ useEffect(() => {
42
+ if (!isLoading && !change && archiveModalState.open) {
43
+ navigate({ to: '/changes' })
44
+ }
45
+ }, [isLoading, change, archiveModalState.open, navigate])
46
+ // TODO: validation 暂时不支持订阅,后续可以添加
47
+ const validation = null as {
48
+ valid: boolean
49
+ issues: Array<{ severity: string; message: string; path?: string }>
50
+ } | null
51
+
52
+ const toggleTaskMutation = useMutation({
53
+ mutationFn: (params: { taskIndex: number; completed: boolean }) =>
54
+ trpcClient.change.toggleTask.mutate({
55
+ changeId,
56
+ taskIndex: params.taskIndex,
57
+ completed: params.completed,
58
+ }),
59
+ // 订阅模式下无需手动 invalidate,文件变更会自动触发更新
60
+ })
61
+
62
+ const handleToggleTask = useCallback(
63
+ (taskIndex: number, completed: boolean) => {
64
+ toggleTaskMutation.mutate({ taskIndex, completed })
65
+ },
66
+ [toggleTaskMutation]
67
+ )
68
+
69
+ const togglingIndex = toggleTaskMutation.isPending
70
+ ? (toggleTaskMutation.variables?.taskIndex ?? null)
71
+ : null
72
+
73
+ // 点击 Archive 按钮:打开全局 Modal
74
+ const handleArchiveClick = useCallback(() => {
75
+ openArchiveModal(changeId, lastChangeNameRef.current)
76
+ }, [changeId, openArchiveModal])
77
+
78
+ const tabs: Tab[] = useMemo(() => {
79
+ if (!change) return []
80
+
81
+ return [
82
+ {
83
+ id: 'overview',
84
+ label: 'Overview',
85
+ icon: <FileText className="h-4 w-4" />,
86
+ content: <ChangeOverview change={change} />,
87
+ },
88
+ {
89
+ id: 'tasks',
90
+ label: `Tasks (${change.progress.completed}/${change.progress.total})`,
91
+ icon: <ListChecks className="h-4 w-4" />,
92
+ content: (
93
+ <TasksView
94
+ tasks={change.tasks}
95
+ progress={change.progress}
96
+ onToggleTask={isStaticMode() ? undefined : handleToggleTask}
97
+ togglingIndex={togglingIndex}
98
+ />
99
+ ),
100
+ },
101
+ {
102
+ id: 'folder',
103
+ label: 'Folder',
104
+ icon: <FolderTree className="h-4 w-4" />,
105
+ content: <FolderEditorViewer changeId={changeId} />,
106
+ },
107
+ ]
108
+ }, [change, changeId, handleToggleTask, togglingIndex])
109
+
110
+ const { selectedTab, setSelectedTab } = useTabsStatusByQuery({
111
+ tabsId: 'changeTab',
112
+ tabs,
113
+ initialTab: tabs[0]?.id,
114
+ })
115
+
116
+ if (firstFrameLoading || (isLoading && !change)) {
117
+ return <div className="route-loading animate-pulse">Loading change...</div>
118
+ }
119
+
120
+ // 当 change 不存在时,显示空白(useEffect 会自动导航到 /changes)
121
+ // 如果 Archive Modal 打开着,用户看到的是 Modal 覆盖的 /changes 页面
122
+ if (!change) {
123
+ return null
124
+ }
125
+
126
+ return (
127
+ <div className="flex min-h-0 flex-1 flex-col gap-6">
128
+ <div className="change-header @container flex items-center justify-between gap-3">
129
+ <div className="flex items-center gap-4">
130
+ <Link to="/changes" className="hover:bg-muted rounded-md p-2">
131
+ <ArrowLeft className="h-5 w-5" />
132
+ </Link>
133
+ <div className="flex flex-col gap-1">
134
+ <h1
135
+ className="font-nav flex items-center gap-2 font-bold"
136
+ style={{ fontSize: 'clamp(1rem, 3cqi, 1.75rem)' }}
137
+ >
138
+ <GitBranch className="h-6 w-6 shrink-0" />
139
+ {change.name}
140
+ </h1>
141
+ <p className="text-muted-foreground" style={{ fontSize: 'clamp(0.7rem, 2cqi, 1rem)' }}>
142
+ ID: {change.id}
143
+ </p>
144
+ </div>
145
+ </div>
146
+
147
+ {/* Hide archive button in static mode */}
148
+ {!isStaticMode() && (
149
+ <button
150
+ onClick={handleArchiveClick}
151
+ className="change-archive-button @sm:gap-2 @sm:px-4 flex h-10 items-center gap-1.5 rounded-md bg-red-600 px-3 py-2 text-white hover:bg-red-700"
152
+ >
153
+ <Archive className="h-4 w-4" />
154
+ <span
155
+ className="change-archive-text @sm:inline hidden"
156
+ style={{ fontSize: 'clamp(0.85rem, 2cqi, 1rem)' }}
157
+ >
158
+ Archive
159
+ </span>
160
+ </button>
161
+ )}
162
+ </div>
163
+
164
+ {validation && !validation.valid && (
165
+ <div className="rounded-lg border border-red-500 bg-red-500/10 p-4">
166
+ <div className="mb-2 flex items-center gap-2 font-medium text-red-600">
167
+ <AlertCircle className="h-5 w-5" />
168
+ Validation Issues
169
+ </div>
170
+ <ul className="space-y-1 text-sm">
171
+ {validation.issues.map((issue, i) => (
172
+ <li key={i} className="text-red-600">
173
+ {issue.message}
174
+ </li>
175
+ ))}
176
+ </ul>
177
+ </div>
178
+ )}
179
+
180
+ <Tabs
181
+ tabs={tabs}
182
+ selectedTab={selectedTab}
183
+ onTabChange={setSelectedTab}
184
+ className="min-h-0 flex-1 gap-6"
185
+ />
186
+ </div>
187
+ )
188
+ }
@@ -0,0 +1,204 @@
1
+ import { useDashboardSubscription, useInitializedSubscription } from '@/lib/use-subscription'
2
+ import { Link } from '@tanstack/react-router'
3
+ import {
4
+ AlertCircle,
5
+ Archive,
6
+ CheckCircle,
7
+ FileText,
8
+ GitBranch,
9
+ LayoutDashboard,
10
+ } from 'lucide-react'
11
+ import { useMemo } from 'react'
12
+
13
+ export function Dashboard() {
14
+ const { data, isLoading, error } = useDashboardSubscription()
15
+ const { data: initialized } = useInitializedSubscription()
16
+
17
+ const sortedSpecs = useMemo(() => {
18
+ if (!data?.specs) return []
19
+ return [...data.specs].sort((a, b) => b.requirements.length - a.requirements.length)
20
+ }, [data?.specs])
21
+
22
+ if (isLoading && !data) {
23
+ return <div className="route-loading animate-pulse">Loading dashboard...</div>
24
+ }
25
+
26
+ if (error) {
27
+ return (
28
+ <div className="text-destructive flex items-center gap-2">
29
+ <AlertCircle className="h-5 w-5" />
30
+ Error loading dashboard: {error.message}
31
+ </div>
32
+ )
33
+ }
34
+
35
+ if (!initialized) {
36
+ return (
37
+ <div className="py-12 text-center">
38
+ <h2 className="font-nav mb-4 flex items-center justify-center gap-2 text-2xl font-bold">
39
+ <LayoutDashboard className="h-6 w-6 shrink-0" />
40
+ OpenSpec Not Initialized
41
+ </h2>
42
+ <p className="text-muted-foreground mb-6">
43
+ This project doesn't have an OpenSpec directory yet.
44
+ </p>
45
+ <Link
46
+ to="/settings"
47
+ className="bg-primary text-primary-foreground inline-flex items-center gap-2 rounded-md px-4 py-2 hover:opacity-90"
48
+ >
49
+ Initialize OpenSpec
50
+ </Link>
51
+ </div>
52
+ )
53
+ }
54
+
55
+ if (!data) return null
56
+
57
+ const { summary } = data
58
+
59
+ return (
60
+ <div className="space-y-6">
61
+ <h1 className="font-nav flex items-center gap-2 text-2xl font-bold">
62
+ <LayoutDashboard className="h-6 w-6 shrink-0" />
63
+ Dashboard
64
+ </h1>
65
+
66
+ {/* Summary Cards */}
67
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
68
+ <StatCard
69
+ icon={<FileText className="h-5 w-5" />}
70
+ label="Specifications"
71
+ value={summary.specCount}
72
+ sublabel={`${summary.requirementCount} requirements`}
73
+ />
74
+ <StatCard
75
+ icon={<GitBranch className="h-5 w-5" />}
76
+ label="Active Changes"
77
+ value={summary.activeChangeCount}
78
+ sublabel="in progress"
79
+ />
80
+ <StatCard
81
+ icon={<Archive className="h-5 w-5" />}
82
+ label="Completed"
83
+ value={summary.archivedChangeCount}
84
+ sublabel="archived"
85
+ />
86
+ <StatCard
87
+ icon={<CheckCircle className="h-5 w-5" />}
88
+ label="Task Progress"
89
+ value={`${summary.completedTasks}/${summary.totalTasks}`}
90
+ sublabel={`${summary.progressPercent}% complete`}
91
+ />
92
+ </div>
93
+
94
+ {/* Active Changes with Progress Bars */}
95
+ <section>
96
+ <div className="mb-3 flex items-center justify-between">
97
+ <h2 className="text-lg font-semibold">Active Changes</h2>
98
+ {data.changes.length > 0 && (
99
+ <Link to="/changes" className="text-primary text-sm hover:underline">
100
+ View all
101
+ </Link>
102
+ )}
103
+ </div>
104
+ <div className="border-border divide-border divide-y rounded-lg border">
105
+ {data.changes.map((change) => {
106
+ const percent =
107
+ change.progress.total > 0
108
+ ? Math.round((change.progress.completed / change.progress.total) * 100)
109
+ : 0
110
+ return (
111
+ <Link
112
+ key={change.id}
113
+ to="/changes/$changeId"
114
+ params={{ changeId: change.id }}
115
+ className="hover:bg-muted/50 block p-4"
116
+ >
117
+ <div className="mb-2 flex items-center justify-between">
118
+ <div className="font-medium">{change.name}</div>
119
+ <span className="text-muted-foreground text-sm">
120
+ {change.progress.completed}/{change.progress.total} tasks
121
+ </span>
122
+ </div>
123
+ <div className="flex items-center gap-3">
124
+ <div className="bg-muted h-2 flex-1 rounded-full">
125
+ <div
126
+ className="bg-primary h-2 rounded-full transition-all"
127
+ style={{ width: `${percent}%` }}
128
+ />
129
+ </div>
130
+ <span className="w-12 text-right text-sm font-medium">{percent}%</span>
131
+ </div>
132
+ </Link>
133
+ )
134
+ })}
135
+ {data.changes.length === 0 && (
136
+ <div className="text-muted-foreground p-4 text-center">
137
+ No active changes. Create one in{' '}
138
+ <code className="bg-muted rounded px-1">openspec/changes/</code>
139
+ </div>
140
+ )}
141
+ </div>
142
+ </section>
143
+
144
+ {/* Specifications sorted by requirement count */}
145
+ <section>
146
+ <div className="mb-3 flex items-center justify-between">
147
+ <h2 className="text-lg font-semibold">Specifications</h2>
148
+ {sortedSpecs.length > 0 && (
149
+ <Link to="/specs" className="text-primary text-sm hover:underline">
150
+ View all
151
+ </Link>
152
+ )}
153
+ </div>
154
+ <div className="border-border divide-border divide-y rounded-lg border">
155
+ {sortedSpecs.map((spec) => (
156
+ <Link
157
+ key={spec.id}
158
+ to="/specs/$specId"
159
+ params={{ specId: spec.id }}
160
+ className="hover:bg-muted/50 flex items-center justify-between p-3"
161
+ >
162
+ <div className="flex items-center gap-3">
163
+ <FileText className="text-muted-foreground h-4 w-4" />
164
+ <span className="font-medium">{spec.name}</span>
165
+ </div>
166
+ <span className="text-muted-foreground text-sm">
167
+ {spec.requirements.length} requirement{spec.requirements.length !== 1 ? 's' : ''}
168
+ </span>
169
+ </Link>
170
+ ))}
171
+ {sortedSpecs.length === 0 && (
172
+ <div className="text-muted-foreground p-4 text-center">
173
+ No specs yet. Create one in{' '}
174
+ <code className="bg-muted rounded px-1">openspec/specs/</code>
175
+ </div>
176
+ )}
177
+ </div>
178
+ </section>
179
+ </div>
180
+ )
181
+ }
182
+
183
+ function StatCard({
184
+ icon,
185
+ label,
186
+ value,
187
+ sublabel,
188
+ }: {
189
+ icon: React.ReactNode
190
+ label: string
191
+ value: number | string
192
+ sublabel: string
193
+ }) {
194
+ return (
195
+ <div className="border-border rounded-lg border p-4">
196
+ <div className="text-muted-foreground mb-2 flex items-center gap-2">
197
+ {icon}
198
+ <span className="text-sm">{label}</span>
199
+ </div>
200
+ <div className="text-2xl font-bold">{value}</div>
201
+ <div className="text-muted-foreground text-sm">{sublabel}</div>
202
+ </div>
203
+ )
204
+ }
@@ -0,0 +1,272 @@
1
+ import { CodeEditor } from '@/components/code-editor'
2
+ import { MarkdownViewer } from '@/components/markdown-viewer'
3
+ import { Tabs, type Tab } from '@/components/tabs'
4
+ import { isStaticMode } from '@/lib/static-mode'
5
+ import { trpcClient } from '@/lib/trpc'
6
+ import { useAgentsMdSubscription, useProjectMdSubscription } from '@/lib/use-subscription'
7
+ import { useMutation } from '@tanstack/react-query'
8
+ import { Bot, Edit2, FileText, Folder, Save, X } from 'lucide-react'
9
+ import { useEffect, useMemo, useState } from 'react'
10
+
11
+ type ActiveTab = 'project' | 'agents'
12
+
13
+ export function Project() {
14
+ const [loading, setLoading] = useState(true)
15
+ const [activeTab, setActiveTab] = useState<ActiveTab>('project')
16
+ const [editingTab, setEditingTab] = useState<ActiveTab | null>(null)
17
+ const [editContent, setEditContent] = useState('')
18
+
19
+ useEffect(() => {
20
+ const id = requestAnimationFrame(() => setLoading(false))
21
+ return () => cancelAnimationFrame(id)
22
+ }, [])
23
+
24
+ const { data: projectMd, isLoading: projectLoading } = useProjectMdSubscription()
25
+ const { data: agentsMd, isLoading: agentsLoading } = useAgentsMdSubscription()
26
+
27
+ const saveProjectMutation = useMutation({
28
+ mutationFn: (content: string) => trpcClient.project.saveProjectMd.mutate({ content }),
29
+ onSuccess: () => {
30
+ // 订阅模式下无需手动 refetch,文件变更会自动触发更新
31
+ setEditingTab(null)
32
+ },
33
+ })
34
+
35
+ const saveAgentsMutation = useMutation({
36
+ mutationFn: (content: string) => trpcClient.project.saveAgentsMd.mutate({ content }),
37
+ onSuccess: () => {
38
+ // 订阅模式下无需手动 refetch,文件变更会自动触发更新
39
+ setEditingTab(null)
40
+ },
41
+ })
42
+
43
+ const currentContent = activeTab === 'project' ? projectMd : agentsMd
44
+ const saveMutation = activeTab === 'project' ? saveProjectMutation : saveAgentsMutation
45
+ const isEditing = editingTab === activeTab
46
+
47
+ const handleEdit = () => {
48
+ setEditContent(currentContent || '')
49
+ setEditingTab(activeTab)
50
+ }
51
+
52
+ const handleSave = () => {
53
+ saveMutation.mutate(editContent)
54
+ }
55
+
56
+ const handleCancel = () => {
57
+ setEditingTab(null)
58
+ setEditContent('')
59
+ }
60
+
61
+ const tabs: Tab[] = useMemo(
62
+ () => [
63
+ {
64
+ id: 'project',
65
+ label: 'project.md',
66
+ icon: <FileText className="h-4 w-4" />,
67
+ content: (
68
+ <div className="border-border h-full min-h-0 flex-1 overflow-hidden rounded-lg border">
69
+ <TabContent
70
+ content={projectMd}
71
+ isLoading={projectLoading}
72
+ isEditing={editingTab === 'project'}
73
+ editContent={editContent}
74
+ setEditContent={setEditContent}
75
+ onCancel={handleCancel}
76
+ onSave={handleSave}
77
+ savePending={saveProjectMutation.isPending}
78
+ tabName="project.md"
79
+ defaultContent="# Project Context\n\n## Purpose\n\n## Tech Stack\n\n## Conventions\n"
80
+ onStartEdit={() => {
81
+ setEditContent(projectMd || '')
82
+ setEditingTab('project')
83
+ }}
84
+ />
85
+ </div>
86
+ ),
87
+ },
88
+ {
89
+ id: 'agents',
90
+ label: 'AGENTS.md',
91
+ icon: <Bot className="h-4 w-4" />,
92
+ content: (
93
+ <div className="border-border h-full min-h-0 flex-1 overflow-hidden rounded-lg border">
94
+ <TabContent
95
+ content={agentsMd}
96
+ isLoading={agentsLoading}
97
+ isEditing={editingTab === 'agents'}
98
+ editContent={editContent}
99
+ setEditContent={setEditContent}
100
+ onCancel={handleCancel}
101
+ onSave={handleSave}
102
+ savePending={saveAgentsMutation.isPending}
103
+ tabName="AGENTS.md"
104
+ defaultContent="# AI Agent Instructions\n\n## Workflow\n\n## Commands\n"
105
+ onStartEdit={() => {
106
+ setEditContent(agentsMd || '')
107
+ setEditingTab('agents')
108
+ }}
109
+ />
110
+ </div>
111
+ ),
112
+ },
113
+ ],
114
+ [
115
+ agentsLoading,
116
+ agentsMd,
117
+ editContent,
118
+ editingTab,
119
+ handleCancel,
120
+ handleSave,
121
+ projectLoading,
122
+ projectMd,
123
+ saveAgentsMutation.isPending,
124
+ saveProjectMutation.isPending,
125
+ ]
126
+ )
127
+
128
+ const descriptions: Record<ActiveTab, React.ReactNode> = useMemo(
129
+ () => ({
130
+ project: (
131
+ <p>
132
+ <strong>project.md</strong> defines project context, tech stack, and conventions for AI
133
+ assistants.
134
+ </p>
135
+ ),
136
+ agents: (
137
+ <p>
138
+ <strong>AGENTS.md</strong> provides workflow instructions for AI coding assistants using
139
+ OpenSpec.
140
+ </p>
141
+ ),
142
+ }),
143
+ []
144
+ )
145
+
146
+ if (projectLoading || agentsLoading) {
147
+ return <div className="route-loading animate-pulse">Loading...</div>
148
+ }
149
+
150
+ if (loading) {
151
+ return <div className="route-loading animate-pulse">Loading project...</div>
152
+ }
153
+
154
+ return (
155
+ <div className="flex min-h-0 flex-1 flex-col gap-6">
156
+ <div className="flex items-center justify-between">
157
+ <h1 className="font-nav flex items-center gap-2 text-2xl font-bold">
158
+ <Folder className="h-6 w-6 shrink-0" />
159
+ Project
160
+ </h1>
161
+ {/* Hide edit button in static mode */}
162
+ {!isStaticMode() && !isEditing && currentContent && (
163
+ <button
164
+ onClick={handleEdit}
165
+ className="border-border hover:bg-muted flex items-center gap-2 rounded-md border px-3 py-1.5 text-sm"
166
+ >
167
+ <Edit2 className="h-4 w-4" />
168
+ Edit
169
+ </button>
170
+ )}
171
+ </div>
172
+
173
+ <Tabs
174
+ tabs={tabs}
175
+ selectedTab={activeTab}
176
+ onTabChange={(id) => setActiveTab(id as ActiveTab)}
177
+ className="min-h-0 flex-1 gap-4"
178
+ />
179
+
180
+ {/* Description */}
181
+ <div className="text-muted-foreground text-sm">{descriptions[activeTab]}</div>
182
+ </div>
183
+ )
184
+ }
185
+
186
+ interface TabContentProps {
187
+ content: string | null | undefined
188
+ isLoading: boolean
189
+ isEditing: boolean
190
+ editContent: string
191
+ setEditContent: (content: string) => void
192
+ onCancel: () => void
193
+ onSave: () => void
194
+ savePending: boolean
195
+ tabName: string
196
+ defaultContent: string
197
+ onStartEdit: () => void
198
+ }
199
+
200
+ function TabContent({
201
+ content,
202
+ isLoading,
203
+ isEditing,
204
+ editContent,
205
+ setEditContent,
206
+ onCancel,
207
+ onSave,
208
+ savePending,
209
+ tabName,
210
+ defaultContent,
211
+ onStartEdit,
212
+ }: TabContentProps) {
213
+ if (isLoading && !content) {
214
+ return <div className="route-loading animate-pulse">Loading...</div>
215
+ }
216
+
217
+ if (!content) {
218
+ return (
219
+ <div className="text-muted-foreground p-8 text-center">
220
+ <p className="mb-4">{tabName} not found.</p>
221
+ {/* Hide create button in static mode */}
222
+ {!isStaticMode() && (
223
+ <button
224
+ onClick={() => {
225
+ setEditContent(defaultContent)
226
+ onStartEdit()
227
+ }}
228
+ className="bg-primary text-primary-foreground rounded-md px-4 py-2 hover:opacity-90"
229
+ >
230
+ Create {tabName}
231
+ </button>
232
+ )}
233
+ </div>
234
+ )
235
+ }
236
+
237
+ if (isEditing) {
238
+ return (
239
+ <div className="flex h-full flex-col">
240
+ <div className="border-border bg-muted/30 flex items-center justify-between border-b p-2">
241
+ <span className="px-2 text-sm font-medium">Editing {tabName}</span>
242
+ <div className="flex gap-2">
243
+ <button
244
+ onClick={onCancel}
245
+ className="border-border hover:bg-muted flex items-center gap-1 rounded border px-3 py-1 text-sm"
246
+ >
247
+ <X className="h-3 w-3" />
248
+ Cancel
249
+ </button>
250
+ <button
251
+ onClick={onSave}
252
+ disabled={savePending}
253
+ className="bg-primary text-primary-foreground flex items-center gap-1 rounded px-3 py-1 text-sm hover:opacity-90 disabled:opacity-50"
254
+ >
255
+ <Save className="h-3 w-3" />
256
+ {savePending ? 'Saving...' : 'Save'}
257
+ </button>
258
+ </div>
259
+ </div>
260
+ <CodeEditor
261
+ value={editContent}
262
+ onChange={setEditContent}
263
+ filename={tabName}
264
+ className="min-h-0 flex-1"
265
+ />
266
+ </div>
267
+ )
268
+ }
269
+
270
+ // 只读模式:使用 CodeEditor 的只读预览
271
+ return <MarkdownViewer markdown={content} />
272
+ }