@rsktash/beads-ui 0.1.1 → 0.1.3

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 (304) hide show
  1. package/dist/assets/abap-DsBKuouk.js +1 -0
  2. package/dist/assets/actionscript-3-D_z4Izcz.js +1 -0
  3. package/dist/assets/ada-727ZlQH0.js +1 -0
  4. package/dist/assets/andromeeda-C3khCPGq.js +1 -0
  5. package/dist/assets/angular-html-LfdN0zeE.js +1 -0
  6. package/dist/assets/angular-ts-CKsD7JZE.js +1 -0
  7. package/dist/assets/apache-Dn00JSTd.js +1 -0
  8. package/dist/assets/apex-COJ4H7py.js +1 -0
  9. package/dist/assets/apl-BBq3IX1j.js +1 -0
  10. package/dist/assets/applescript-Bu5BbsvL.js +1 -0
  11. package/dist/assets/ara-7O62HKoU.js +1 -0
  12. package/dist/assets/asciidoc-BPT9niGB.js +1 -0
  13. package/dist/assets/asm-Dhn9LcZ4.js +1 -0
  14. package/dist/assets/astro-CqkE3fuf.js +1 -0
  15. package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
  16. package/dist/assets/awk-eg146-Ew.js +1 -0
  17. package/dist/assets/ayu-dark-Cv9koXgw.js +1 -0
  18. package/dist/assets/ballerina-Du268qiB.js +1 -0
  19. package/dist/assets/bat-fje9CFhw.js +1 -0
  20. package/dist/assets/beancount-BwXTMy5W.js +1 -0
  21. package/dist/assets/berry-3xVqZejG.js +1 -0
  22. package/dist/assets/bibtex-xW4inM5L.js +1 -0
  23. package/dist/assets/bicep-DHo0CJ0O.js +1 -0
  24. package/dist/assets/blade-a8OxSdnT.js +1 -0
  25. package/dist/assets/bsl-Dgyn0ogV.js +1 -0
  26. package/dist/assets/c-C3t2pwGQ.js +1 -0
  27. package/dist/assets/cadence-DNquZEk8.js +1 -0
  28. package/dist/assets/cairo--RitsXJZ.js +1 -0
  29. package/dist/assets/catppuccin-frappe-CD_QflpE.js +1 -0
  30. package/dist/assets/catppuccin-latte-DRW-0cLl.js +1 -0
  31. package/dist/assets/catppuccin-macchiato-C-_shW-Y.js +1 -0
  32. package/dist/assets/catppuccin-mocha-LGGdnPYs.js +1 -0
  33. package/dist/assets/clarity-BHOwM8T6.js +1 -0
  34. package/dist/assets/clojure-DxSadP1t.js +1 -0
  35. package/dist/assets/cmake-DbXoA79R.js +1 -0
  36. package/dist/assets/cobol-PTqiYgYu.js +1 -0
  37. package/dist/assets/codeowners-Bp6g37R7.js +1 -0
  38. package/dist/assets/codeql-sacFqUAJ.js +1 -0
  39. package/dist/assets/coffee-dyiR41kL.js +1 -0
  40. package/dist/assets/common-lisp-C7gG9l05.js +1 -0
  41. package/dist/assets/coq-Dsg_Bt_b.js +1 -0
  42. package/dist/assets/cpp-BksuvNSY.js +1 -0
  43. package/dist/assets/crystal-DtDmRg-F.js +1 -0
  44. package/dist/assets/csharp-D9R-vmeu.js +1 -0
  45. package/dist/assets/css-BPhBrDlE.js +1 -0
  46. package/dist/assets/csv-B0qRVHPH.js +1 -0
  47. package/dist/assets/cue-DtFQj3wx.js +1 -0
  48. package/dist/assets/cypher-m2LEI-9-.js +1 -0
  49. package/dist/assets/d-BoXegm-a.js +1 -0
  50. package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  51. package/dist/assets/dart-B9wLZaAG.js +1 -0
  52. package/dist/assets/dax-ClGRhx96.js +1 -0
  53. package/dist/assets/desktop-DEIpsLCJ.js +1 -0
  54. package/dist/assets/diff-BgYniUM_.js +1 -0
  55. package/dist/assets/docker-COcR7UxN.js +1 -0
  56. package/dist/assets/dotenv-BjQB5zDj.js +1 -0
  57. package/dist/assets/dracula-BzJJZx-M.js +1 -0
  58. package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
  59. package/dist/assets/dream-maker-C-nORZOA.js +1 -0
  60. package/dist/assets/edge-D5gP-w-T.js +1 -0
  61. package/dist/assets/elixir-CLiX3zqd.js +1 -0
  62. package/dist/assets/elm-CmHSxxaM.js +1 -0
  63. package/dist/assets/emacs-lisp-BX77sIaO.js +1 -0
  64. package/dist/assets/erb-BYTLMnw6.js +1 -0
  65. package/dist/assets/erlang-B-DoSBHF.js +1 -0
  66. package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
  67. package/dist/assets/everforest-light-C8M2exoo.js +1 -0
  68. package/dist/assets/fennel-bCA53EVm.js +1 -0
  69. package/dist/assets/fish-w-ucz2PV.js +1 -0
  70. package/dist/assets/fluent-Dayu4EKP.js +1 -0
  71. package/dist/assets/fortran-fixed-form-TqA4NnZg.js +1 -0
  72. package/dist/assets/fortran-free-form-DKXYxT9g.js +1 -0
  73. package/dist/assets/fsharp-XplgxFYe.js +1 -0
  74. package/dist/assets/gdresource-BHYsBjWJ.js +1 -0
  75. package/dist/assets/gdscript-DfxzS6Rs.js +1 -0
  76. package/dist/assets/gdshader-SKMF96pI.js +1 -0
  77. package/dist/assets/genie-ajMbGru0.js +1 -0
  78. package/dist/assets/gherkin--30QC5Em.js +1 -0
  79. package/dist/assets/git-commit-i4q6IMui.js +1 -0
  80. package/dist/assets/git-rebase-B-v9cOL2.js +1 -0
  81. package/dist/assets/github-dark-DHJKELXO.js +1 -0
  82. package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
  83. package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  84. package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  85. package/dist/assets/github-light-DAi9KRSo.js +1 -0
  86. package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
  87. package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  88. package/dist/assets/gleam-B430Bg39.js +1 -0
  89. package/dist/assets/glimmer-js-D-cwc0-E.js +1 -0
  90. package/dist/assets/glimmer-ts-pgjy16dm.js +1 -0
  91. package/dist/assets/glsl-DBO2IWDn.js +1 -0
  92. package/dist/assets/gnuplot-CM8KxXT1.js +1 -0
  93. package/dist/assets/go-B1SYOhNW.js +1 -0
  94. package/dist/assets/graphql-cDcHW_If.js +1 -0
  95. package/dist/assets/groovy-DkBy-JyN.js +1 -0
  96. package/dist/assets/hack-D1yCygmZ.js +1 -0
  97. package/dist/assets/haml-B2EZWmdv.js +1 -0
  98. package/dist/assets/handlebars-BQGss363.js +1 -0
  99. package/dist/assets/haskell-BILxekzW.js +1 -0
  100. package/dist/assets/haxe-C5wWYbrZ.js +1 -0
  101. package/dist/assets/hcl-HzYwdGDm.js +1 -0
  102. package/dist/assets/hjson-T-Tgc4AT.js +1 -0
  103. package/dist/assets/hlsl-ifBTmRxC.js +1 -0
  104. package/dist/assets/houston-DnULxvSX.js +1 -0
  105. package/dist/assets/html-C2L_23MC.js +1 -0
  106. package/dist/assets/html-derivative-CSfWNPLT.js +1 -0
  107. package/dist/assets/http-FRrOvY1W.js +1 -0
  108. package/dist/assets/hxml-TIA70rKU.js +1 -0
  109. package/dist/assets/hy-BMj5Y0dO.js +1 -0
  110. package/dist/assets/imba-bv_oIlVt.js +1 -0
  111. package/dist/assets/index-BSNzF5KT.js +121 -0
  112. package/dist/assets/index-Ux6DCth7.css +1 -0
  113. package/dist/assets/ini-BjABl1g7.js +1 -0
  114. package/dist/assets/java-xI-RfyKK.js +1 -0
  115. package/dist/assets/javascript-ySlJ1b_l.js +1 -0
  116. package/dist/assets/jinja-DGy0s7-h.js +1 -0
  117. package/dist/assets/jison-BqZprYcd.js +1 -0
  118. package/dist/assets/json-BQoSv7ci.js +1 -0
  119. package/dist/assets/json5-w8dY5SsB.js +1 -0
  120. package/dist/assets/jsonc-TU54ms6u.js +1 -0
  121. package/dist/assets/jsonl-DREVFZK8.js +1 -0
  122. package/dist/assets/jsonnet-BfivnA6A.js +1 -0
  123. package/dist/assets/jssm-P4WzXJd0.js +1 -0
  124. package/dist/assets/jsx-BAng5TT0.js +1 -0
  125. package/dist/assets/julia-BBuGR-5E.js +1 -0
  126. package/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  127. package/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  128. package/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
  129. package/dist/assets/kotlin-B5lbUyaz.js +1 -0
  130. package/dist/assets/kusto-mebxcVVE.js +1 -0
  131. package/dist/assets/laserwave-DUszq2jm.js +1 -0
  132. package/dist/assets/latex-C-cWTeAZ.js +1 -0
  133. package/dist/assets/lean-XBlWyCtg.js +1 -0
  134. package/dist/assets/less-BfCpw3nA.js +1 -0
  135. package/dist/assets/light-plus-B7mTdjB0.js +1 -0
  136. package/dist/assets/liquid-D3W5UaiH.js +1 -0
  137. package/dist/assets/log-Cc5clBb7.js +1 -0
  138. package/dist/assets/logo-IuBKFhSY.js +1 -0
  139. package/dist/assets/lua-CvWAzNxB.js +1 -0
  140. package/dist/assets/luau-Du5NY7AG.js +1 -0
  141. package/dist/assets/make-Bvotw-X0.js +1 -0
  142. package/dist/assets/markdown-UIAJJxZW.js +1 -0
  143. package/dist/assets/marko-z0MBrx5-.js +1 -0
  144. package/dist/assets/material-theme-D5KoaKCx.js +1 -0
  145. package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
  146. package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  147. package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
  148. package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  149. package/dist/assets/matlab-D9-PGadD.js +1 -0
  150. package/dist/assets/mdc-DB_EDNY_.js +1 -0
  151. package/dist/assets/mdx-sdHcTMYB.js +1 -0
  152. package/dist/assets/mermaid-Ci6OQyBP.js +1 -0
  153. package/dist/assets/min-dark-CafNBF8u.js +1 -0
  154. package/dist/assets/min-light-CTRr51gU.js +1 -0
  155. package/dist/assets/mipsasm-BC5c_5Pe.js +1 -0
  156. package/dist/assets/mojo-Tz6hzZYG.js +1 -0
  157. package/dist/assets/monokai-D4h5O-jR.js +1 -0
  158. package/dist/assets/move-DB_GagMm.js +1 -0
  159. package/dist/assets/narrat-DLbgOhZU.js +1 -0
  160. package/dist/assets/nextflow-B0XVJmRM.js +1 -0
  161. package/dist/assets/nginx-D_VnBJ67.js +1 -0
  162. package/dist/assets/night-owl-C39BiMTA.js +1 -0
  163. package/dist/assets/nim-ZlGxZxc3.js +1 -0
  164. package/dist/assets/nix-shcSOmrb.js +1 -0
  165. package/dist/assets/nord-Ddv68eIx.js +1 -0
  166. package/dist/assets/nushell-D4Tzg5kh.js +1 -0
  167. package/dist/assets/objective-c-Deuh7S70.js +1 -0
  168. package/dist/assets/objective-cpp-BUEGK8hf.js +1 -0
  169. package/dist/assets/ocaml-BNioltXt.js +1 -0
  170. package/dist/assets/one-dark-pro-GBQ2dnAY.js +1 -0
  171. package/dist/assets/one-light-PoHY5YXO.js +1 -0
  172. package/dist/assets/pascal-JqZropPD.js +1 -0
  173. package/dist/assets/perl-CHQXSrWU.js +1 -0
  174. package/dist/assets/php-B5ebYQev.js +1 -0
  175. package/dist/assets/plastic-3e1v2bzS.js +1 -0
  176. package/dist/assets/plsql-LKU2TuZ1.js +1 -0
  177. package/dist/assets/po-BFLt1xDp.js +1 -0
  178. package/dist/assets/poimandres-CS3Unz2-.js +1 -0
  179. package/dist/assets/polar-DKykz6zU.js +1 -0
  180. package/dist/assets/postcss-B3ZDOciz.js +1 -0
  181. package/dist/assets/powerquery-CSHBycmS.js +1 -0
  182. package/dist/assets/powershell-BIEUsx6d.js +1 -0
  183. package/dist/assets/prisma-B48N-Iqd.js +1 -0
  184. package/dist/assets/prolog-BY-TUvya.js +1 -0
  185. package/dist/assets/proto-zocC4JxJ.js +1 -0
  186. package/dist/assets/pug-CM9l7STV.js +1 -0
  187. package/dist/assets/puppet-Cza_XSSt.js +1 -0
  188. package/dist/assets/purescript-Bg-kzb6g.js +1 -0
  189. package/dist/assets/python-DhUJRlN_.js +1 -0
  190. package/dist/assets/qml-D8XfuvdV.js +1 -0
  191. package/dist/assets/qmldir-C8lEn-DE.js +1 -0
  192. package/dist/assets/qss-DhMKtDLN.js +1 -0
  193. package/dist/assets/r-CwjWoCRV.js +1 -0
  194. package/dist/assets/racket-CzouJOBO.js +1 -0
  195. package/dist/assets/raku-B1bQXN8T.js +1 -0
  196. package/dist/assets/razor-CNLDkMZG.js +1 -0
  197. package/dist/assets/red-bN70gL4F.js +1 -0
  198. package/dist/assets/reg-5LuOXUq_.js +1 -0
  199. package/dist/assets/regexp-DWJ3fJO_.js +1 -0
  200. package/dist/assets/rel-DJlmqQ1C.js +1 -0
  201. package/dist/assets/riscv-QhoSD0DR.js +1 -0
  202. package/dist/assets/rose-pine-CmCqftbK.js +1 -0
  203. package/dist/assets/rose-pine-dawn-Ds-gbosJ.js +1 -0
  204. package/dist/assets/rose-pine-moon-CjDtw9vr.js +1 -0
  205. package/dist/assets/rst-4NLicBqY.js +1 -0
  206. package/dist/assets/ruby-DeZ3UC14.js +1 -0
  207. package/dist/assets/rust-Be6lgOlo.js +1 -0
  208. package/dist/assets/sas-BmTFh92c.js +1 -0
  209. package/dist/assets/sass-BJ4Li9vH.js +1 -0
  210. package/dist/assets/scala-DQVVAn-B.js +1 -0
  211. package/dist/assets/scheme-BJGe-b2p.js +1 -0
  212. package/dist/assets/scss-C31hgJw-.js +1 -0
  213. package/dist/assets/sdbl-BLhTXw86.js +1 -0
  214. package/dist/assets/shaderlab-B7qAK45m.js +1 -0
  215. package/dist/assets/shellscript-atvbtKCR.js +1 -0
  216. package/dist/assets/shellsession-C_rIy8kc.js +1 -0
  217. package/dist/assets/slack-dark-BthQWCQV.js +1 -0
  218. package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
  219. package/dist/assets/smalltalk-DkLiglaE.js +1 -0
  220. package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
  221. package/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
  222. package/dist/assets/solarized-light-L9t79GZl.js +1 -0
  223. package/dist/assets/solidity-C1w2a3ep.js +1 -0
  224. package/dist/assets/soy-C-lX7w71.js +1 -0
  225. package/dist/assets/sparql-bYkjHRlG.js +1 -0
  226. package/dist/assets/splunk-Cf8iN4DR.js +1 -0
  227. package/dist/assets/sql-COK4E0Yg.js +1 -0
  228. package/dist/assets/ssh-config-BknIz3MU.js +1 -0
  229. package/dist/assets/stata-DorPZHa4.js +1 -0
  230. package/dist/assets/stylus-BeQkCIfX.js +1 -0
  231. package/dist/assets/svelte-MSaWC3Je.js +1 -0
  232. package/dist/assets/swift-BSxZ-RaX.js +1 -0
  233. package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
  234. package/dist/assets/system-verilog-C7L56vO4.js +1 -0
  235. package/dist/assets/systemd-CUnW07Te.js +1 -0
  236. package/dist/assets/talonscript-C1XDQQGZ.js +1 -0
  237. package/dist/assets/tasl-CQjiPCtT.js +1 -0
  238. package/dist/assets/tcl-DQ1-QYvQ.js +1 -0
  239. package/dist/assets/templ-dwX3ZSMB.js +1 -0
  240. package/dist/assets/terraform-BbSNqyBO.js +1 -0
  241. package/dist/assets/tex-rYs2v40G.js +1 -0
  242. package/dist/assets/tokyo-night-DBQeEorK.js +1 -0
  243. package/dist/assets/toml-CB2ApiWb.js +1 -0
  244. package/dist/assets/ts-tags-CipyTH0X.js +1 -0
  245. package/dist/assets/tsv-B_m7g4N7.js +1 -0
  246. package/dist/assets/tsx-B6W0miNI.js +1 -0
  247. package/dist/assets/turtle-BMR_PYu6.js +1 -0
  248. package/dist/assets/twig-NC5TFiHP.js +1 -0
  249. package/dist/assets/typescript-Dj6nwHGl.js +1 -0
  250. package/dist/assets/typespec-BpWG_bgh.js +1 -0
  251. package/dist/assets/typst-BVUVsWT6.js +1 -0
  252. package/dist/assets/v-CAQ2eGtk.js +1 -0
  253. package/dist/assets/vala-BFOHcciG.js +1 -0
  254. package/dist/assets/vb-CdO5JTpU.js +1 -0
  255. package/dist/assets/verilog-CJaU5se_.js +1 -0
  256. package/dist/assets/vesper-BEBZ7ncR.js +1 -0
  257. package/dist/assets/vhdl-DYoNaHQp.js +1 -0
  258. package/dist/assets/viml-m4uW47V2.js +1 -0
  259. package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
  260. package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
  261. package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
  262. package/dist/assets/vue-BuYVFjOK.js +1 -0
  263. package/dist/assets/vue-html-xdeiXROB.js +1 -0
  264. package/dist/assets/vyper-nyqBNV6O.js +1 -0
  265. package/dist/assets/wasm-C6j12Q_x.js +1 -0
  266. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  267. package/dist/assets/wenyan-7A4Fjokl.js +1 -0
  268. package/dist/assets/wgsl-CB0Krxn9.js +1 -0
  269. package/dist/assets/wikitext-DCE3LsBG.js +1 -0
  270. package/dist/assets/wolfram-C3FkfJm5.js +1 -0
  271. package/dist/assets/xml-e3z08dGr.js +1 -0
  272. package/dist/assets/xsl-Dd0NUgwM.js +1 -0
  273. package/dist/assets/yaml-CVw76BM1.js +1 -0
  274. package/dist/assets/zenscript-HnGAYVZD.js +1 -0
  275. package/dist/assets/zig-BVz_zdnA.js +1 -0
  276. package/{client → dist}/index.html +2 -1
  277. package/package.json +9 -2
  278. package/server/dolt-pool.js +69 -13
  279. package/.github/workflows/publish.yml +0 -28
  280. package/client/postcss.config.js +0 -11
  281. package/client/src/App.tsx +0 -35
  282. package/client/src/components/IssueCard.tsx +0 -73
  283. package/client/src/components/Layout.tsx +0 -175
  284. package/client/src/components/Markdown.tsx +0 -77
  285. package/client/src/components/PriorityBadge.tsx +0 -26
  286. package/client/src/components/SearchDialog.tsx +0 -137
  287. package/client/src/components/SectionEditor.tsx +0 -212
  288. package/client/src/components/StatusBadge.tsx +0 -64
  289. package/client/src/components/TypeBadge.tsx +0 -26
  290. package/client/src/hooks/use-mutation.ts +0 -55
  291. package/client/src/hooks/use-search.ts +0 -19
  292. package/client/src/hooks/use-subscription.ts +0 -187
  293. package/client/src/index.css +0 -133
  294. package/client/src/lib/avatar.ts +0 -17
  295. package/client/src/lib/types.ts +0 -115
  296. package/client/src/lib/ws-client.ts +0 -214
  297. package/client/src/lib/ws-context.tsx +0 -28
  298. package/client/src/main.tsx +0 -10
  299. package/client/src/views/Board.tsx +0 -200
  300. package/client/src/views/Detail.tsx +0 -398
  301. package/client/src/views/List.tsx +0 -461
  302. package/client/tailwind.config.ts +0 -68
  303. package/client/tsconfig.json +0 -16
  304. package/client/vite.config.ts +0 -20
@@ -1,187 +0,0 @@
1
- import { useEffect, useState, useRef } from "react";
2
- import { useWs } from "../lib/ws-context";
3
- import type { Issue, SubscriptionType, PushEvent } from "../lib/types";
4
- import { WsClient } from "../lib/ws-client";
5
-
6
- /**
7
- * Shared subscription cache. Multiple useSubscription() calls with the same
8
- * type+params share a single server-side subscription. The cache lives on
9
- * the WsClient instance (via WeakMap) so it's scoped to the connection.
10
- */
11
-
12
- interface SharedSub {
13
- refCount: number;
14
- subId: string;
15
- items: Map<string, Issue>;
16
- listeners: Set<() => void>;
17
- loading: boolean;
18
- total: number;
19
- cleanupPush: (() => void) | null;
20
- }
21
-
22
- const clientCaches = new WeakMap<WsClient, Map<string, SharedSub>>();
23
-
24
- function getCache(ws: WsClient): Map<string, SharedSub> {
25
- let cache = clientCaches.get(ws);
26
- if (!cache) {
27
- cache = new Map();
28
- clientCaches.set(ws, cache);
29
- }
30
- return cache;
31
- }
32
-
33
- function cacheKey(
34
- type: SubscriptionType,
35
- params?: Record<string, string | number | boolean>,
36
- ): string {
37
- return params ? `${type}:${JSON.stringify(params)}` : type;
38
- }
39
-
40
- function acquireSubscription(
41
- ws: WsClient,
42
- type: SubscriptionType,
43
- params?: Record<string, string | number | boolean>,
44
- ): SharedSub {
45
- const cache = getCache(ws);
46
- const key = cacheKey(type, params);
47
-
48
- let shared = cache.get(key);
49
- if (shared) {
50
- shared.refCount++;
51
- return shared;
52
- }
53
-
54
- const subId = `sub-${type}-${Date.now()}`;
55
- shared = {
56
- refCount: 1,
57
- subId,
58
- items: new Map(),
59
- listeners: new Set(),
60
- loading: true,
61
- total: 0,
62
- cleanupPush: null,
63
- };
64
-
65
- const notify = () => {
66
- for (const listener of shared!.listeners) listener();
67
- };
68
-
69
- shared.cleanupPush = ws.onPush((event: PushEvent) => {
70
- if (event.id !== subId) return;
71
-
72
- if (event.type === "snapshot") {
73
- shared!.items = new Map();
74
- for (const issue of event.issues) shared!.items.set(issue.id, issue);
75
- shared!.loading = false;
76
- if (typeof event.total === "number") {
77
- shared!.total = event.total;
78
- } else {
79
- shared!.total = event.issues.length;
80
- }
81
- notify();
82
- } else if (event.type === "upsert") {
83
- shared!.items.set(event.issue.id, event.issue);
84
- notify();
85
- } else if (event.type === "delete") {
86
- shared!.items.delete(event.issue_id);
87
- notify();
88
- }
89
- });
90
-
91
- ws.subscribe({ id: subId, type, params });
92
- cache.set(key, shared);
93
- return shared;
94
- }
95
-
96
- function releaseSubscription(
97
- ws: WsClient,
98
- type: SubscriptionType,
99
- params?: Record<string, string | number | boolean>,
100
- ): void {
101
- const cache = getCache(ws);
102
- const key = cacheKey(type, params);
103
- const shared = cache.get(key);
104
- if (!shared) return;
105
-
106
- shared.refCount--;
107
- if (shared.refCount <= 0) {
108
- shared.cleanupPush?.();
109
- ws.unsubscribe(shared.subId);
110
- cache.delete(key);
111
- }
112
- }
113
-
114
- export function useSubscription(
115
- type: SubscriptionType,
116
- params?: Record<string, string | number | boolean>,
117
- ): { issues: Issue[]; loading: boolean; refreshing: boolean; total: number } {
118
- const ws = useWs();
119
- const paramsKey = params ? JSON.stringify(params) : "";
120
- const sharedRef = useRef<SharedSub | null>(null);
121
-
122
- // Track whether we've ever received data (initial load vs refresh)
123
- const hasLoadedOnceRef = useRef(false);
124
- const [staleIssues, setStaleIssues] = useState<Issue[]>([]);
125
- const [staleTotal, setStaleTotal] = useState(0);
126
-
127
- // Acquire/release shared subscription on mount/unmount
128
- useEffect(() => {
129
- const shared = acquireSubscription(ws, type, params);
130
- sharedRef.current = shared;
131
- return () => {
132
- releaseSubscription(ws, type, params);
133
- sharedRef.current = null;
134
- };
135
- // eslint-disable-next-line react-hooks/exhaustive-deps
136
- }, [ws, type, paramsKey]);
137
-
138
- // Subscribe to changes
139
- const [issues, setIssues] = useState<Issue[]>([]);
140
- const [loading, setLoading] = useState(true);
141
- const [total, setTotal] = useState(0);
142
-
143
- useEffect(() => {
144
- const shared = sharedRef.current;
145
- if (!shared) return;
146
-
147
- const currentIssues = Array.from(shared.items.values());
148
- setIssues(currentIssues);
149
- setLoading(shared.loading);
150
- setTotal(shared.total);
151
-
152
- // When new data arrives, save it as stale for next transition
153
- if (!shared.loading && currentIssues.length > 0) {
154
- hasLoadedOnceRef.current = true;
155
- setStaleIssues(currentIssues);
156
- setStaleTotal(shared.total);
157
- }
158
-
159
- const listener = () => {
160
- const items = Array.from(shared.items.values());
161
- setIssues(items);
162
- setLoading(shared.loading);
163
- setTotal(shared.total);
164
-
165
- if (!shared.loading && items.length > 0) {
166
- hasLoadedOnceRef.current = true;
167
- setStaleIssues(items);
168
- setStaleTotal(shared.total);
169
- }
170
- };
171
- shared.listeners.add(listener);
172
- return () => {
173
- shared.listeners.delete(listener);
174
- };
175
- // eslint-disable-next-line react-hooks/exhaustive-deps
176
- }, [ws, type, paramsKey]);
177
-
178
- const isInitialLoad = loading && !hasLoadedOnceRef.current;
179
- const isRefreshing = loading && hasLoadedOnceRef.current;
180
-
181
- return {
182
- issues: isRefreshing ? staleIssues : issues,
183
- loading: isInitialLoad,
184
- refreshing: isRefreshing,
185
- total: isRefreshing ? staleTotal : total,
186
- };
187
- }
@@ -1,133 +0,0 @@
1
- @import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
2
-
3
- @tailwind base;
4
- @tailwind components;
5
- @tailwind utilities;
6
-
7
- @layer base {
8
- :root {
9
- /* Background layers */
10
- --bg-base: #FDFBF7;
11
- --bg-surface: #F8F5EF;
12
- --bg-elevated: #FFFFFF;
13
- --bg-hover: #F5F0E8;
14
- --bg-overlay: rgba(0,0,0,0.4);
15
-
16
- /* Text */
17
- --text-primary: #1A1A1A;
18
- --text-secondary: #6B6B6B;
19
- --text-tertiary: #9CA3AF;
20
- --text-inverse: #FAFAF9;
21
-
22
- /* Borders */
23
- --border-subtle: rgba(0,0,0,0.06);
24
- --border-default: rgba(0,0,0,0.1);
25
-
26
- /* Accent */
27
- --accent: #16A34A;
28
- --accent-soft: rgba(22,163,74,0.1);
29
-
30
- /* Status */
31
- --status-open: #3B82F6;
32
- --status-in-progress: #F59E0B;
33
- --status-blocked: #EF4444;
34
- --status-closed: #22C55E;
35
-
36
- /* Priority */
37
- --priority-0: #EF4444;
38
- --priority-1: #F97316;
39
- --priority-2: #3B82F6;
40
- --priority-3: #22C55E;
41
- --priority-4: #D1D5DB;
42
-
43
- /* Type */
44
- --type-task: #16A34A;
45
- --type-bug: #EF4444;
46
- --type-feature: #6366F1;
47
- --type-epic: #7C3AED;
48
- --type-chore: #78716C;
49
-
50
- /* Spacing (4px grid) */
51
- --space-1: 4px;
52
- --space-2: 8px;
53
- --space-3: 12px;
54
- --space-4: 16px;
55
- --space-5: 20px;
56
- --space-6: 24px;
57
- --space-8: 32px;
58
- --space-10: 40px;
59
- --space-12: 48px;
60
- --space-16: 64px;
61
-
62
- /* Radius */
63
- --radius-sm: 6px;
64
- --radius-md: 10px;
65
- --radius-lg: 14px;
66
-
67
- /* Shadows */
68
- --shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
69
- --shadow-card: 0 1px 3px rgba(0,0,0,0.04), 0 0 0 1px rgba(0,0,0,0.03);
70
- --shadow-md: 0 4px 12px rgba(0,0,0,0.06);
71
-
72
- /* Transitions */
73
- --transition-fast: 120ms ease;
74
- --transition-medium: 200ms cubic-bezier(0.16, 1, 0.3, 1);
75
- }
76
-
77
- body {
78
- font-family: 'DM Sans', system-ui, -apple-system, sans-serif;
79
- background: var(--bg-base);
80
- color: var(--text-primary);
81
- -webkit-font-smoothing: antialiased;
82
- -moz-osx-font-smoothing: grayscale;
83
- }
84
- }
85
-
86
- /* IssueCard styles */
87
- .issue-card {
88
- background: var(--bg-elevated);
89
- border: 1px solid var(--border-subtle);
90
- border-radius: var(--radius-md);
91
- box-shadow: var(--shadow-card);
92
- transition: box-shadow 0.2s cubic-bezier(0.16, 1, 0.3, 1), transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
93
- }
94
-
95
- .issue-card:hover {
96
- box-shadow: var(--shadow-md);
97
- transform: translateY(-1px);
98
- }
99
-
100
- .issue-card--dimmed {
101
- opacity: 0.6;
102
- }
103
-
104
- /* Skeleton shimmer */
105
- .skeleton-shimmer {
106
- background: linear-gradient(
107
- 90deg,
108
- var(--bg-hover) 25%,
109
- var(--bg-elevated) 50%,
110
- var(--bg-hover) 75%
111
- );
112
- background-size: 200% 100%;
113
- animation: shimmer 1.5s ease-in-out infinite;
114
- }
115
-
116
- @keyframes shimmer {
117
- 0% { background-position: 200% 0; }
118
- 100% { background-position: -200% 0; }
119
- }
120
-
121
- @keyframes spin {
122
- from { transform: rotate(0deg); }
123
- to { transform: rotate(360deg); }
124
- }
125
-
126
- .issue-card__handle {
127
- opacity: 0;
128
- transition: opacity 120ms ease;
129
- }
130
-
131
- .issue-card:hover .issue-card__handle {
132
- opacity: 0.6;
133
- }
@@ -1,17 +0,0 @@
1
- const AVATAR_COLORS = [
2
- "#E57373", "#F06292", "#BA68C8", "#9575CD",
3
- "#7986CB", "#64B5F6", "#4FC3F7", "#4DD0E1",
4
- "#4DB6AC", "#81C784", "#AED581", "#FFB74D",
5
- ];
6
-
7
- export function getInitials(name: string): string {
8
- const parts = name.trim().split(/\s+/);
9
- if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
10
- return name.slice(0, 2).toUpperCase();
11
- }
12
-
13
- export function getAvatarColor(name: string): string {
14
- let hash = 0;
15
- for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash);
16
- return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length];
17
- }
@@ -1,115 +0,0 @@
1
- // Issue shape from server
2
- export interface Issue {
3
- id: string;
4
- title: string;
5
- description?: string;
6
- status: "open" | "in_progress" | "blocked" | "closed";
7
- priority: number;
8
- issue_type: string;
9
- assignee?: string;
10
- owner?: string;
11
- created_at: string | number;
12
- updated_at: string | number;
13
- closed_at?: string | number | null;
14
- labels?: string[];
15
- // Epic fields
16
- total_children?: number;
17
- closed_children?: number;
18
- eligible_for_close?: boolean;
19
- // Detail fields
20
- acceptance?: string;
21
- notes?: string;
22
- design?: string;
23
- dependencies?: Array<{ id: string; title: string; status: string }>;
24
- dependents?: Array<{ id: string; title: string; status: string }>;
25
- comments?: Comment[];
26
- parent_id?: string;
27
- parent_title?: string;
28
- parent_status?: string;
29
- parent_type?: string;
30
- }
31
-
32
- export interface Comment {
33
- id: string;
34
- issue_id: string;
35
- author: string;
36
- text: string;
37
- created_at: number;
38
- updated_at: number;
39
- }
40
-
41
- // Subscription types
42
- export type SubscriptionType =
43
- | "all-issues"
44
- | "epics"
45
- | "blocked-issues"
46
- | "ready-issues"
47
- | "in-progress-issues"
48
- | "closed-issues"
49
- | "search-issues"
50
- | "issue-detail";
51
-
52
- export interface SubscriptionSpec {
53
- id: string;
54
- type: SubscriptionType;
55
- params?: Record<string, string | number | boolean>;
56
- }
57
-
58
- // Message envelopes
59
- export interface RequestEnvelope {
60
- id: string;
61
- type: string;
62
- payload?: unknown;
63
- }
64
-
65
- export interface ReplyEnvelope {
66
- id: string;
67
- ok: boolean;
68
- type: string;
69
- payload?: unknown;
70
- error?: { code: string; message: string; details?: unknown };
71
- }
72
-
73
- // Push event payloads
74
- export interface SnapshotPayload {
75
- type: "snapshot";
76
- id: string;
77
- revision: number;
78
- issues: Issue[];
79
- total?: number;
80
- }
81
-
82
- export interface UpsertPayload {
83
- type: "upsert";
84
- id: string;
85
- revision: number;
86
- issue: Issue;
87
- }
88
-
89
- export interface DeletePayload {
90
- type: "delete";
91
- id: string;
92
- revision: number;
93
- issue_id: string;
94
- }
95
-
96
- export type PushEvent = SnapshotPayload | UpsertPayload | DeletePayload;
97
-
98
- // Mutation payloads
99
- export interface UpdateStatusPayload {
100
- id: string;
101
- status: "open" | "in_progress" | "closed";
102
- }
103
-
104
- export interface EditTextPayload {
105
- id: string;
106
- field: "title" | "description" | "acceptance" | "notes" | "design";
107
- value: string;
108
- }
109
-
110
- export interface CreateIssuePayload {
111
- title: string;
112
- type?: string;
113
- priority?: number;
114
- description?: string;
115
- }
@@ -1,214 +0,0 @@
1
- import type {
2
- ReplyEnvelope,
3
- RequestEnvelope,
4
- SubscriptionSpec,
5
- PushEvent,
6
- UpdateStatusPayload,
7
- EditTextPayload,
8
- CreateIssuePayload,
9
- Issue,
10
- Comment,
11
- } from "./types";
12
-
13
- type PushHandler = (event: PushEvent) => void;
14
- type ConnectionHandler = (connected: boolean) => void;
15
-
16
- let counter = 0;
17
- function nextId(): string {
18
- return `req-${Date.now()}-${++counter}`;
19
- }
20
-
21
- export class WsClient {
22
- private ws: WebSocket | null = null;
23
- private pending = new Map<
24
- string,
25
- { resolve: (v: unknown) => void; reject: (e: Error) => void }
26
- >();
27
- private sendQueue: string[] = [];
28
- private pushHandlers = new Set<PushHandler>();
29
- private connectionHandlers = new Set<ConnectionHandler>();
30
- private activeSubscriptions = new Map<string, SubscriptionSpec>();
31
- private reconnectDelay = 500;
32
- private url: string;
33
- private disposed = false;
34
-
35
- constructor() {
36
- const proto = location.protocol === "https:" ? "wss:" : "ws:";
37
- this.url = `${proto}//${location.host}/ws`;
38
- }
39
-
40
- connect(): void {
41
- if (this.disposed) return;
42
- this.ws = new WebSocket(this.url);
43
- this.ws.onopen = () => {
44
- this.reconnectDelay = 500;
45
- // Flush queued messages
46
- for (const msg of this.sendQueue) {
47
- this.ws?.send(msg);
48
- }
49
- this.sendQueue = [];
50
- // Resubscribe all active subscriptions
51
- for (const [, spec] of this.activeSubscriptions) {
52
- this.rawSend("subscribe-list", spec);
53
- }
54
- for (const h of this.connectionHandlers) h(true);
55
- };
56
- this.ws.onclose = () => {
57
- for (const h of this.connectionHandlers) h(false);
58
- this.scheduleReconnect();
59
- };
60
- this.ws.onmessage = (e) => {
61
- this.handleMessage(JSON.parse(e.data));
62
- };
63
- }
64
-
65
- dispose(): void {
66
- this.disposed = true;
67
- this.ws?.close();
68
- this.ws = null;
69
- for (const [, p] of this.pending) {
70
- p.reject(new Error("disposed"));
71
- }
72
- this.pending.clear();
73
- this.sendQueue = [];
74
- }
75
-
76
- private scheduleReconnect(): void {
77
- if (this.disposed) return;
78
- setTimeout(() => this.connect(), this.reconnectDelay);
79
- this.reconnectDelay = Math.min(this.reconnectDelay * 2, 10000);
80
- for (const [id, p] of this.pending) {
81
- p.reject(new Error("disconnected"));
82
- this.pending.delete(id);
83
- }
84
- }
85
-
86
- private handleMessage(msg: ReplyEnvelope): void {
87
- if (
88
- msg.type === "snapshot" ||
89
- msg.type === "upsert" ||
90
- msg.type === "delete"
91
- ) {
92
- const event = msg.payload as PushEvent;
93
- for (const handler of this.pushHandlers) handler(event);
94
- return;
95
- }
96
- const pending = this.pending.get(msg.id);
97
- if (pending) {
98
- this.pending.delete(msg.id);
99
- if (msg.ok) pending.resolve(msg.payload);
100
- else pending.reject(new Error(msg.error?.message || "unknown error"));
101
- }
102
- }
103
-
104
- /** Send without promise tracking (used for resubscribe on reconnect) */
105
- private rawSend(type: string, payload?: unknown): void {
106
- const id = nextId();
107
- const envelope: RequestEnvelope = { id, type, payload };
108
- const msg = JSON.stringify(envelope);
109
- if (this.ws?.readyState === WebSocket.OPEN) {
110
- this.ws.send(msg);
111
- } else {
112
- this.sendQueue.push(msg);
113
- }
114
- }
115
-
116
- private send<T = unknown>(type: string, payload?: unknown): Promise<T> {
117
- return new Promise((resolve, reject) => {
118
- const id = nextId();
119
- this.pending.set(id, {
120
- resolve: resolve as (v: unknown) => void,
121
- reject,
122
- });
123
- const envelope: RequestEnvelope = { id, type, payload };
124
- const msg = JSON.stringify(envelope);
125
- if (this.ws?.readyState === WebSocket.OPEN) {
126
- this.ws.send(msg);
127
- } else {
128
- this.sendQueue.push(msg);
129
- }
130
- });
131
- }
132
-
133
- onConnection(handler: ConnectionHandler): () => void {
134
- this.connectionHandlers.add(handler);
135
- return () => this.connectionHandlers.delete(handler);
136
- }
137
-
138
- onPush(handler: PushHandler): () => void {
139
- this.pushHandlers.add(handler);
140
- return () => this.pushHandlers.delete(handler);
141
- }
142
-
143
- // Subscriptions — tracked for auto-resubscribe on reconnect
144
- subscribe(spec: SubscriptionSpec): Promise<{ id: string; key: string }> {
145
- this.activeSubscriptions.set(spec.id, spec);
146
- return this.send("subscribe-list", spec);
147
- }
148
-
149
- unsubscribe(id: string): Promise<void> {
150
- this.activeSubscriptions.delete(id);
151
- return this.send("unsubscribe-list", { id });
152
- }
153
-
154
- // Mutations
155
- updateStatus(payload: UpdateStatusPayload): Promise<Issue> {
156
- return this.send("update-status", payload);
157
- }
158
-
159
- editText(payload: EditTextPayload): Promise<Issue> {
160
- return this.send("edit-text", payload);
161
- }
162
-
163
- createIssue(payload: CreateIssuePayload): Promise<{ created: boolean }> {
164
- return this.send("create-issue", payload);
165
- }
166
-
167
- updatePriority(id: string, priority: number): Promise<Issue> {
168
- return this.send("update-priority", { id, priority });
169
- }
170
-
171
- updateAssignee(id: string, assignee: string): Promise<Issue> {
172
- return this.send("update-assignee", { id, assignee });
173
- }
174
-
175
- addLabel(id: string, label: string): Promise<Issue> {
176
- return this.send("label-add", { id, label });
177
- }
178
-
179
- removeLabel(id: string, label: string): Promise<Issue> {
180
- return this.send("label-remove", { id, label });
181
- }
182
-
183
- deleteIssue(id: string): Promise<{ deleted: boolean; id: string }> {
184
- return this.send("delete-issue", { id });
185
- }
186
-
187
- getComments(id: string): Promise<Comment[]> {
188
- return this.send("get-comments", { id });
189
- }
190
-
191
- addComment(id: string, text: string): Promise<Comment[]> {
192
- return this.send("add-comment", { id, text });
193
- }
194
-
195
- addDep(a: string, b: string): Promise<Issue> {
196
- return this.send("dep-add", { a, b });
197
- }
198
-
199
- removeDep(a: string, b: string): Promise<Issue> {
200
- return this.send("dep-remove", { a, b });
201
- }
202
-
203
- listWorkspaces(): Promise<unknown> {
204
- return this.send("list-workspaces");
205
- }
206
-
207
- getWorkspace(): Promise<unknown> {
208
- return this.send("get-workspace");
209
- }
210
-
211
- setWorkspace(path: string, database: string): Promise<unknown> {
212
- return this.send("set-workspace", { path, database });
213
- }
214
- }
@@ -1,28 +0,0 @@
1
- import {
2
- createContext,
3
- useContext,
4
- useRef,
5
- type ReactNode,
6
- } from "react";
7
- import { WsClient } from "./ws-client";
8
-
9
- const WsContext = createContext<WsClient | null>(null);
10
-
11
- export function WsProvider({ children }: { children: ReactNode }) {
12
- const clientRef = useRef<WsClient | null>(null);
13
- if (!clientRef.current) {
14
- clientRef.current = new WsClient();
15
- clientRef.current.connect();
16
- }
17
- return (
18
- <WsContext.Provider value={clientRef.current}>
19
- {children}
20
- </WsContext.Provider>
21
- );
22
- }
23
-
24
- export function useWs(): WsClient {
25
- const client = useContext(WsContext);
26
- if (!client) throw new Error("useWs must be inside WsProvider");
27
- return client;
28
- }
@@ -1,10 +0,0 @@
1
- import { StrictMode } from "react";
2
- import { createRoot } from "react-dom/client";
3
- import { App } from "./App";
4
- import "./index.css";
5
-
6
- createRoot(document.getElementById("root")!).render(
7
- <StrictMode>
8
- <App />
9
- </StrictMode>
10
- );