@theokit/ui 0.13.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 (715) hide show
  1. package/CHANGELOG.md +1325 -0
  2. package/DESIGN.md +456 -0
  3. package/LICENSE +201 -0
  4. package/NOTICE +38 -0
  5. package/README.md +467 -0
  6. package/dist/chunk-27ENTTY7.js +146 -0
  7. package/dist/chunk-27ENTTY7.js.map +1 -0
  8. package/dist/chunk-2H6TQELG.js +33 -0
  9. package/dist/chunk-2H6TQELG.js.map +1 -0
  10. package/dist/chunk-2L6MRJD4.js +120 -0
  11. package/dist/chunk-2L6MRJD4.js.map +1 -0
  12. package/dist/chunk-2Y5V2PAL.js +80 -0
  13. package/dist/chunk-2Y5V2PAL.js.map +1 -0
  14. package/dist/chunk-34NAFDVL.js +46 -0
  15. package/dist/chunk-34NAFDVL.js.map +1 -0
  16. package/dist/chunk-36KJGXEK.js +112 -0
  17. package/dist/chunk-36KJGXEK.js.map +1 -0
  18. package/dist/chunk-3BMYYNN6.js +124 -0
  19. package/dist/chunk-3BMYYNN6.js.map +1 -0
  20. package/dist/chunk-3OHV7EEI.js +34 -0
  21. package/dist/chunk-3OHV7EEI.js.map +1 -0
  22. package/dist/chunk-3QKTS6F5.js +88 -0
  23. package/dist/chunk-3QKTS6F5.js.map +1 -0
  24. package/dist/chunk-3TBXLYNM.js +42 -0
  25. package/dist/chunk-3TBXLYNM.js.map +1 -0
  26. package/dist/chunk-4AM2HSXU.js +67 -0
  27. package/dist/chunk-4AM2HSXU.js.map +1 -0
  28. package/dist/chunk-4BCGKM65.js +44 -0
  29. package/dist/chunk-4BCGKM65.js.map +1 -0
  30. package/dist/chunk-4D3JILQX.js +145 -0
  31. package/dist/chunk-4D3JILQX.js.map +1 -0
  32. package/dist/chunk-4EJU2GBG.js +48 -0
  33. package/dist/chunk-4EJU2GBG.js.map +1 -0
  34. package/dist/chunk-4WKO3G5C.js +110 -0
  35. package/dist/chunk-4WKO3G5C.js.map +1 -0
  36. package/dist/chunk-53XPKI7Q.js +97 -0
  37. package/dist/chunk-53XPKI7Q.js.map +1 -0
  38. package/dist/chunk-55TDVDPG.js +58 -0
  39. package/dist/chunk-55TDVDPG.js.map +1 -0
  40. package/dist/chunk-56BJLFW7.js +26 -0
  41. package/dist/chunk-56BJLFW7.js.map +1 -0
  42. package/dist/chunk-5HOQLE6Y.js +35 -0
  43. package/dist/chunk-5HOQLE6Y.js.map +1 -0
  44. package/dist/chunk-5TY3NYF5.js +144 -0
  45. package/dist/chunk-5TY3NYF5.js.map +1 -0
  46. package/dist/chunk-5VOSCJKQ.js +92 -0
  47. package/dist/chunk-5VOSCJKQ.js.map +1 -0
  48. package/dist/chunk-65NVO6TK.js +171 -0
  49. package/dist/chunk-65NVO6TK.js.map +1 -0
  50. package/dist/chunk-6A5TPCKP.js +64 -0
  51. package/dist/chunk-6A5TPCKP.js.map +1 -0
  52. package/dist/chunk-6CO4LEXZ.js +41 -0
  53. package/dist/chunk-6CO4LEXZ.js.map +1 -0
  54. package/dist/chunk-6FVUPNPG.js +56 -0
  55. package/dist/chunk-6FVUPNPG.js.map +1 -0
  56. package/dist/chunk-76YWTIWK.js +106 -0
  57. package/dist/chunk-76YWTIWK.js.map +1 -0
  58. package/dist/chunk-7EI7424P.js +78 -0
  59. package/dist/chunk-7EI7424P.js.map +1 -0
  60. package/dist/chunk-AHTVYOPQ.js +26 -0
  61. package/dist/chunk-AHTVYOPQ.js.map +1 -0
  62. package/dist/chunk-AJTJNHKK.js +85 -0
  63. package/dist/chunk-AJTJNHKK.js.map +1 -0
  64. package/dist/chunk-AMT3CPMC.js +155 -0
  65. package/dist/chunk-AMT3CPMC.js.map +1 -0
  66. package/dist/chunk-AX5EH73R.js +59 -0
  67. package/dist/chunk-AX5EH73R.js.map +1 -0
  68. package/dist/chunk-B3VAJSZ2.js +35 -0
  69. package/dist/chunk-B3VAJSZ2.js.map +1 -0
  70. package/dist/chunk-B4CQMQ64.js +25 -0
  71. package/dist/chunk-B4CQMQ64.js.map +1 -0
  72. package/dist/chunk-BMRZXT5T.js +115 -0
  73. package/dist/chunk-BMRZXT5T.js.map +1 -0
  74. package/dist/chunk-BYZ6OFH4.js +73 -0
  75. package/dist/chunk-BYZ6OFH4.js.map +1 -0
  76. package/dist/chunk-C55VUQ7N.js +156 -0
  77. package/dist/chunk-C55VUQ7N.js.map +1 -0
  78. package/dist/chunk-D4GEAV4C.js +91 -0
  79. package/dist/chunk-D4GEAV4C.js.map +1 -0
  80. package/dist/chunk-DC43CHAM.js +152 -0
  81. package/dist/chunk-DC43CHAM.js.map +1 -0
  82. package/dist/chunk-DKCRLN35.js +92 -0
  83. package/dist/chunk-DKCRLN35.js.map +1 -0
  84. package/dist/chunk-DN5BUDBI.js +86 -0
  85. package/dist/chunk-DN5BUDBI.js.map +1 -0
  86. package/dist/chunk-DOLKDYMS.js +88 -0
  87. package/dist/chunk-DOLKDYMS.js.map +1 -0
  88. package/dist/chunk-DW34WXCG.js +28 -0
  89. package/dist/chunk-DW34WXCG.js.map +1 -0
  90. package/dist/chunk-DZAAKHGZ.js +135 -0
  91. package/dist/chunk-DZAAKHGZ.js.map +1 -0
  92. package/dist/chunk-E4IRSSHO.js +116 -0
  93. package/dist/chunk-E4IRSSHO.js.map +1 -0
  94. package/dist/chunk-E67WQXBV.js +104 -0
  95. package/dist/chunk-E67WQXBV.js.map +1 -0
  96. package/dist/chunk-EG6IHP3H.js +128 -0
  97. package/dist/chunk-EG6IHP3H.js.map +1 -0
  98. package/dist/chunk-EO7LOXG2.js +82 -0
  99. package/dist/chunk-EO7LOXG2.js.map +1 -0
  100. package/dist/chunk-EWDN56AS.js +24 -0
  101. package/dist/chunk-EWDN56AS.js.map +1 -0
  102. package/dist/chunk-F5P5P2SC.js +141 -0
  103. package/dist/chunk-F5P5P2SC.js.map +1 -0
  104. package/dist/chunk-FAWPRZTM.js +79 -0
  105. package/dist/chunk-FAWPRZTM.js.map +1 -0
  106. package/dist/chunk-FGYJ2WPX.js +36 -0
  107. package/dist/chunk-FGYJ2WPX.js.map +1 -0
  108. package/dist/chunk-GBG3I5I5.js +46 -0
  109. package/dist/chunk-GBG3I5I5.js.map +1 -0
  110. package/dist/chunk-GDMCDW66.js +19 -0
  111. package/dist/chunk-GDMCDW66.js.map +1 -0
  112. package/dist/chunk-H6HSQCOW.js +80 -0
  113. package/dist/chunk-H6HSQCOW.js.map +1 -0
  114. package/dist/chunk-HDM4RCIF.js +111 -0
  115. package/dist/chunk-HDM4RCIF.js.map +1 -0
  116. package/dist/chunk-HNTOGGVD.js +77 -0
  117. package/dist/chunk-HNTOGGVD.js.map +1 -0
  118. package/dist/chunk-HQW2ABO4.js +28 -0
  119. package/dist/chunk-HQW2ABO4.js.map +1 -0
  120. package/dist/chunk-HRDRGZ2Y.js +76 -0
  121. package/dist/chunk-HRDRGZ2Y.js.map +1 -0
  122. package/dist/chunk-HUOVA7SF.js +83 -0
  123. package/dist/chunk-HUOVA7SF.js.map +1 -0
  124. package/dist/chunk-ITA3SNOR.js +133 -0
  125. package/dist/chunk-ITA3SNOR.js.map +1 -0
  126. package/dist/chunk-IYNUPG2G.js +61 -0
  127. package/dist/chunk-IYNUPG2G.js.map +1 -0
  128. package/dist/chunk-JJ65ZI4P.js +199 -0
  129. package/dist/chunk-JJ65ZI4P.js.map +1 -0
  130. package/dist/chunk-JRBGZ6NI.js +106 -0
  131. package/dist/chunk-JRBGZ6NI.js.map +1 -0
  132. package/dist/chunk-K45OO62F.js +108 -0
  133. package/dist/chunk-K45OO62F.js.map +1 -0
  134. package/dist/chunk-KDTKA667.js +67 -0
  135. package/dist/chunk-KDTKA667.js.map +1 -0
  136. package/dist/chunk-KI7KZBSN.js +142 -0
  137. package/dist/chunk-KI7KZBSN.js.map +1 -0
  138. package/dist/chunk-KOJ7XOPZ.js +87 -0
  139. package/dist/chunk-KOJ7XOPZ.js.map +1 -0
  140. package/dist/chunk-KQTHJ22B.js +82 -0
  141. package/dist/chunk-KQTHJ22B.js.map +1 -0
  142. package/dist/chunk-KRC43RZR.js +77 -0
  143. package/dist/chunk-KRC43RZR.js.map +1 -0
  144. package/dist/chunk-LJQOEGQ2.js +116 -0
  145. package/dist/chunk-LJQOEGQ2.js.map +1 -0
  146. package/dist/chunk-LKRNUSKZ.js +149 -0
  147. package/dist/chunk-LKRNUSKZ.js.map +1 -0
  148. package/dist/chunk-LLL7QQ52.js +76 -0
  149. package/dist/chunk-LLL7QQ52.js.map +1 -0
  150. package/dist/chunk-LQ4B5X4Y.js +56 -0
  151. package/dist/chunk-LQ4B5X4Y.js.map +1 -0
  152. package/dist/chunk-M3FSLEHQ.js +76 -0
  153. package/dist/chunk-M3FSLEHQ.js.map +1 -0
  154. package/dist/chunk-M5G3O6H6.js +57 -0
  155. package/dist/chunk-M5G3O6H6.js.map +1 -0
  156. package/dist/chunk-M6JIC5PU.js +81 -0
  157. package/dist/chunk-M6JIC5PU.js.map +1 -0
  158. package/dist/chunk-N2HJ3SLS.js +186 -0
  159. package/dist/chunk-N2HJ3SLS.js.map +1 -0
  160. package/dist/chunk-NGZWBFTP.js +45 -0
  161. package/dist/chunk-NGZWBFTP.js.map +1 -0
  162. package/dist/chunk-OAKCXT35.js +34 -0
  163. package/dist/chunk-OAKCXT35.js.map +1 -0
  164. package/dist/chunk-OSD3U3HT.js +54 -0
  165. package/dist/chunk-OSD3U3HT.js.map +1 -0
  166. package/dist/chunk-OUXESQ2R.js +42 -0
  167. package/dist/chunk-OUXESQ2R.js.map +1 -0
  168. package/dist/chunk-OY2LJHMJ.js +43 -0
  169. package/dist/chunk-OY2LJHMJ.js.map +1 -0
  170. package/dist/chunk-OYEZR4CN.js +221 -0
  171. package/dist/chunk-OYEZR4CN.js.map +1 -0
  172. package/dist/chunk-P57HUMAE.js +66 -0
  173. package/dist/chunk-P57HUMAE.js.map +1 -0
  174. package/dist/chunk-P6Y2PI6L.js +82 -0
  175. package/dist/chunk-P6Y2PI6L.js.map +1 -0
  176. package/dist/chunk-PA7TDXUQ.js +51 -0
  177. package/dist/chunk-PA7TDXUQ.js.map +1 -0
  178. package/dist/chunk-PPBGGNPV.js +112 -0
  179. package/dist/chunk-PPBGGNPV.js.map +1 -0
  180. package/dist/chunk-PRH4HKND.js +48 -0
  181. package/dist/chunk-PRH4HKND.js.map +1 -0
  182. package/dist/chunk-PSPAZJUQ.js +32 -0
  183. package/dist/chunk-PSPAZJUQ.js.map +1 -0
  184. package/dist/chunk-Q5G5CGZ2.js +170 -0
  185. package/dist/chunk-Q5G5CGZ2.js.map +1 -0
  186. package/dist/chunk-QDAF3LP7.js +89 -0
  187. package/dist/chunk-QDAF3LP7.js.map +1 -0
  188. package/dist/chunk-QGVIGNJ3.js +37 -0
  189. package/dist/chunk-QGVIGNJ3.js.map +1 -0
  190. package/dist/chunk-QNUITYSY.js +68 -0
  191. package/dist/chunk-QNUITYSY.js.map +1 -0
  192. package/dist/chunk-QSWVN3RT.js +116 -0
  193. package/dist/chunk-QSWVN3RT.js.map +1 -0
  194. package/dist/chunk-QTLQZ7OJ.js +110 -0
  195. package/dist/chunk-QTLQZ7OJ.js.map +1 -0
  196. package/dist/chunk-QYAMLIG2.js +84 -0
  197. package/dist/chunk-QYAMLIG2.js.map +1 -0
  198. package/dist/chunk-REILH4XF.js +128 -0
  199. package/dist/chunk-REILH4XF.js.map +1 -0
  200. package/dist/chunk-S6SSK6QX.js +80 -0
  201. package/dist/chunk-S6SSK6QX.js.map +1 -0
  202. package/dist/chunk-SA7ED3PN.js +68 -0
  203. package/dist/chunk-SA7ED3PN.js.map +1 -0
  204. package/dist/chunk-SIJOEM4N.js +55 -0
  205. package/dist/chunk-SIJOEM4N.js.map +1 -0
  206. package/dist/chunk-SLOKAAH2.js +70 -0
  207. package/dist/chunk-SLOKAAH2.js.map +1 -0
  208. package/dist/chunk-TR6NPSMX.js +85 -0
  209. package/dist/chunk-TR6NPSMX.js.map +1 -0
  210. package/dist/chunk-TSZ5DEAT.js +106 -0
  211. package/dist/chunk-TSZ5DEAT.js.map +1 -0
  212. package/dist/chunk-TUNVF45W.js +127 -0
  213. package/dist/chunk-TUNVF45W.js.map +1 -0
  214. package/dist/chunk-TXOBNSQ5.js +63 -0
  215. package/dist/chunk-TXOBNSQ5.js.map +1 -0
  216. package/dist/chunk-U44DRLMM.js +88 -0
  217. package/dist/chunk-U44DRLMM.js.map +1 -0
  218. package/dist/chunk-U4THNRV5.js +114 -0
  219. package/dist/chunk-U4THNRV5.js.map +1 -0
  220. package/dist/chunk-UAZOFC4W.js +72 -0
  221. package/dist/chunk-UAZOFC4W.js.map +1 -0
  222. package/dist/chunk-UGKI466V.js +12 -0
  223. package/dist/chunk-UGKI466V.js.map +1 -0
  224. package/dist/chunk-VM4RMQQN.js +11 -0
  225. package/dist/chunk-VM4RMQQN.js.map +1 -0
  226. package/dist/chunk-VQ37VLAS.js +54 -0
  227. package/dist/chunk-VQ37VLAS.js.map +1 -0
  228. package/dist/chunk-VT7VSYH5.js +73 -0
  229. package/dist/chunk-VT7VSYH5.js.map +1 -0
  230. package/dist/chunk-VTIRUCLZ.js +57 -0
  231. package/dist/chunk-VTIRUCLZ.js.map +1 -0
  232. package/dist/chunk-VVBAEYKI.js +202 -0
  233. package/dist/chunk-VVBAEYKI.js.map +1 -0
  234. package/dist/chunk-WHFIQUCC.js +120 -0
  235. package/dist/chunk-WHFIQUCC.js.map +1 -0
  236. package/dist/chunk-WPSESV5Z.js +74 -0
  237. package/dist/chunk-WPSESV5Z.js.map +1 -0
  238. package/dist/chunk-WXEXCHEN.js +51 -0
  239. package/dist/chunk-WXEXCHEN.js.map +1 -0
  240. package/dist/chunk-X2DDPD3D.js +113 -0
  241. package/dist/chunk-X2DDPD3D.js.map +1 -0
  242. package/dist/chunk-X7VIMKLD.js +127 -0
  243. package/dist/chunk-X7VIMKLD.js.map +1 -0
  244. package/dist/chunk-XJ3EG6XY.js +30 -0
  245. package/dist/chunk-XJ3EG6XY.js.map +1 -0
  246. package/dist/chunk-XOT5HWSF.js +23 -0
  247. package/dist/chunk-XOT5HWSF.js.map +1 -0
  248. package/dist/chunk-Y72IP43U.js +117 -0
  249. package/dist/chunk-Y72IP43U.js.map +1 -0
  250. package/dist/chunk-YD6FLXBV.js +61 -0
  251. package/dist/chunk-YD6FLXBV.js.map +1 -0
  252. package/dist/chunk-YEQQGYYO.js +1022 -0
  253. package/dist/chunk-YEQQGYYO.js.map +1 -0
  254. package/dist/chunk-YYW6AEIT.js +46 -0
  255. package/dist/chunk-YYW6AEIT.js.map +1 -0
  256. package/dist/chunk-ZEVGXKRU.js +104 -0
  257. package/dist/chunk-ZEVGXKRU.js.map +1 -0
  258. package/dist/chunk-ZKSMMLDP.js +74 -0
  259. package/dist/chunk-ZKSMMLDP.js.map +1 -0
  260. package/dist/chunk-ZU6IM6PK.js +101 -0
  261. package/dist/chunk-ZU6IM6PK.js.map +1 -0
  262. package/dist/chunk-ZUS5KZGO.js +714 -0
  263. package/dist/chunk-ZUS5KZGO.js.map +1 -0
  264. package/dist/chunk-ZVS2GOT2.js +58 -0
  265. package/dist/chunk-ZVS2GOT2.js.map +1 -0
  266. package/dist/chunk-ZXPDS6DH.js +3 -0
  267. package/dist/chunk-ZXPDS6DH.js.map +1 -0
  268. package/dist/chunk-ZZQQJX5Z.js +173 -0
  269. package/dist/chunk-ZZQQJX5Z.js.map +1 -0
  270. package/dist/components.css +2 -0
  271. package/dist/composites/account-menu/index.js +6 -0
  272. package/dist/composites/account-menu/index.js.map +1 -0
  273. package/dist/composites/agent-composer/index.js +7 -0
  274. package/dist/composites/agent-composer/index.js.map +1 -0
  275. package/dist/composites/agent-editor/index.js +10 -0
  276. package/dist/composites/agent-editor/index.js.map +1 -0
  277. package/dist/composites/agent-stream/index.js +12 -0
  278. package/dist/composites/agent-stream/index.js.map +1 -0
  279. package/dist/composites/agent-timeline/index.js +5 -0
  280. package/dist/composites/agent-timeline/index.js.map +1 -0
  281. package/dist/composites/approval-card/index.js +5 -0
  282. package/dist/composites/approval-card/index.js.map +1 -0
  283. package/dist/composites/chat-composer/index.js +6 -0
  284. package/dist/composites/chat-composer/index.js.map +1 -0
  285. package/dist/composites/chat-message/index.js +6 -0
  286. package/dist/composites/chat-message/index.js.map +1 -0
  287. package/dist/composites/code-block/index.js +5 -0
  288. package/dist/composites/code-block/index.js.map +1 -0
  289. package/dist/composites/command-palette/index.js +5 -0
  290. package/dist/composites/command-palette/index.js.map +1 -0
  291. package/dist/composites/confirm-dialog/index.js +7 -0
  292. package/dist/composites/confirm-dialog/index.js.map +1 -0
  293. package/dist/composites/cron-jobs-list/index.js +5 -0
  294. package/dist/composites/cron-jobs-list/index.js.map +1 -0
  295. package/dist/composites/data-table/index.js +10 -0
  296. package/dist/composites/data-table/index.js.map +1 -0
  297. package/dist/composites/deployment-row/index.js +5 -0
  298. package/dist/composites/deployment-row/index.js.map +1 -0
  299. package/dist/composites/domain-config/index.js +7 -0
  300. package/dist/composites/domain-config/index.js.map +1 -0
  301. package/dist/composites/env-var-editor/index.js +7 -0
  302. package/dist/composites/env-var-editor/index.js.map +1 -0
  303. package/dist/composites/mcp-server-list/index.js +5 -0
  304. package/dist/composites/mcp-server-list/index.js.map +1 -0
  305. package/dist/composites/page-shell/index.js +7 -0
  306. package/dist/composites/page-shell/index.js.map +1 -0
  307. package/dist/composites/permission-modal/index.js +6 -0
  308. package/dist/composites/permission-modal/index.js.map +1 -0
  309. package/dist/composites/preview-env-card/index.js +6 -0
  310. package/dist/composites/preview-env-card/index.js.map +1 -0
  311. package/dist/composites/preview-panel/index.js +5 -0
  312. package/dist/composites/preview-panel/index.js.map +1 -0
  313. package/dist/composites/project-card/index.js +6 -0
  314. package/dist/composites/project-card/index.js.map +1 -0
  315. package/dist/composites/rollback-ui/index.js +6 -0
  316. package/dist/composites/rollback-ui/index.js.map +1 -0
  317. package/dist/composites/rule-editor/index.js +11 -0
  318. package/dist/composites/rule-editor/index.js.map +1 -0
  319. package/dist/composites/skill-editor/index.js +11 -0
  320. package/dist/composites/skill-editor/index.js.map +1 -0
  321. package/dist/composites/skills-list/index.js +5 -0
  322. package/dist/composites/skills-list/index.js.map +1 -0
  323. package/dist/composites/stability-bundle-viewer/index.js +4 -0
  324. package/dist/composites/stability-bundle-viewer/index.js.map +1 -0
  325. package/dist/composites/task-header/index.js +5 -0
  326. package/dist/composites/task-header/index.js.map +1 -0
  327. package/dist/composites/usage-meter/index.js +5 -0
  328. package/dist/composites/usage-meter/index.js.map +1 -0
  329. package/dist/fonts/LICENSE-GEIST.txt +92 -0
  330. package/dist/fonts/geist-400.woff2 +0 -0
  331. package/dist/fonts/geist-500.woff2 +0 -0
  332. package/dist/fonts/geist-600.woff2 +0 -0
  333. package/dist/fonts/geist-mono-400.woff2 +0 -0
  334. package/dist/fonts/geist-mono-500.woff2 +0 -0
  335. package/dist/fonts/geist-mono-600.woff2 +0 -0
  336. package/dist/fonts-cdn.css +28 -0
  337. package/dist/fonts.css +75 -0
  338. package/dist/index.d.ts +4621 -0
  339. package/dist/index.js +1338 -0
  340. package/dist/index.js.map +1 -0
  341. package/dist/plugin-D5xmXqYb.d.ts +172 -0
  342. package/dist/preset-v3-legacy.d.ts +35 -0
  343. package/dist/preset-v3-legacy.js +159 -0
  344. package/dist/preset-v3-legacy.js.map +1 -0
  345. package/dist/preset.css +27 -0
  346. package/dist/primitives/action-bar/index.js +4 -0
  347. package/dist/primitives/action-bar/index.js.map +1 -0
  348. package/dist/primitives/agent-error-card/index.js +5 -0
  349. package/dist/primitives/agent-error-card/index.js.map +1 -0
  350. package/dist/primitives/agent-event/index.js +4 -0
  351. package/dist/primitives/agent-event/index.js.map +1 -0
  352. package/dist/primitives/agent-handoff/index.js +4 -0
  353. package/dist/primitives/agent-handoff/index.js.map +1 -0
  354. package/dist/primitives/agent-profile/index.js +4 -0
  355. package/dist/primitives/agent-profile/index.js.map +1 -0
  356. package/dist/primitives/agent-starting-state/index.js +5 -0
  357. package/dist/primitives/agent-starting-state/index.js.map +1 -0
  358. package/dist/primitives/agent-streaming/index.js +5 -0
  359. package/dist/primitives/agent-streaming/index.js.map +1 -0
  360. package/dist/primitives/alert/index.js +4 -0
  361. package/dist/primitives/alert/index.js.map +1 -0
  362. package/dist/primitives/artifact-preview/index.js +4 -0
  363. package/dist/primitives/artifact-preview/index.js.map +1 -0
  364. package/dist/primitives/attachment-chip/index.js +4 -0
  365. package/dist/primitives/attachment-chip/index.js.map +1 -0
  366. package/dist/primitives/audit-log-entry/index.js +4 -0
  367. package/dist/primitives/audit-log-entry/index.js.map +1 -0
  368. package/dist/primitives/auto-compact-notice/index.js +5 -0
  369. package/dist/primitives/auto-compact-notice/index.js.map +1 -0
  370. package/dist/primitives/avatar/index.js +4 -0
  371. package/dist/primitives/avatar/index.js.map +1 -0
  372. package/dist/primitives/badge/index.js +4 -0
  373. package/dist/primitives/badge/index.js.map +1 -0
  374. package/dist/primitives/branch-indicator/index.js +4 -0
  375. package/dist/primitives/branch-indicator/index.js.map +1 -0
  376. package/dist/primitives/browser-controls/index.js +4 -0
  377. package/dist/primitives/browser-controls/index.js.map +1 -0
  378. package/dist/primitives/build-log-stream/index.js +5 -0
  379. package/dist/primitives/build-log-stream/index.js.map +1 -0
  380. package/dist/primitives/button/index.js +4 -0
  381. package/dist/primitives/button/index.js.map +1 -0
  382. package/dist/primitives/capability-indicator/index.js +4 -0
  383. package/dist/primitives/capability-indicator/index.js.map +1 -0
  384. package/dist/primitives/card/index.js +4 -0
  385. package/dist/primitives/card/index.js.map +1 -0
  386. package/dist/primitives/channel-card/index.js +4 -0
  387. package/dist/primitives/channel-card/index.js.map +1 -0
  388. package/dist/primitives/chat-thread/index.js +5 -0
  389. package/dist/primitives/chat-thread/index.js.map +1 -0
  390. package/dist/primitives/checkbox/index.js +4 -0
  391. package/dist/primitives/checkbox/index.js.map +1 -0
  392. package/dist/primitives/context-card/index.js +4 -0
  393. package/dist/primitives/context-card/index.js.map +1 -0
  394. package/dist/primitives/context-window-bar/index.js +4 -0
  395. package/dist/primitives/context-window-bar/index.js.map +1 -0
  396. package/dist/primitives/copy-button/index.js +4 -0
  397. package/dist/primitives/copy-button/index.js.map +1 -0
  398. package/dist/primitives/cost-meter/index.js +4 -0
  399. package/dist/primitives/cost-meter/index.js.map +1 -0
  400. package/dist/primitives/created-files-card/index.js +4 -0
  401. package/dist/primitives/created-files-card/index.js.map +1 -0
  402. package/dist/primitives/cron-job-card/index.js +4 -0
  403. package/dist/primitives/cron-job-card/index.js.map +1 -0
  404. package/dist/primitives/danger-zone/index.js +4 -0
  405. package/dist/primitives/danger-zone/index.js.map +1 -0
  406. package/dist/primitives/dialog/index.js +4 -0
  407. package/dist/primitives/dialog/index.js.map +1 -0
  408. package/dist/primitives/diff-viewer/index.js +4 -0
  409. package/dist/primitives/diff-viewer/index.js.map +1 -0
  410. package/dist/primitives/dropdown-menu/index.js +4 -0
  411. package/dist/primitives/dropdown-menu/index.js.map +1 -0
  412. package/dist/primitives/empty-state/index.js +4 -0
  413. package/dist/primitives/empty-state/index.js.map +1 -0
  414. package/dist/primitives/export-chat-dialog/index.js +4 -0
  415. package/dist/primitives/export-chat-dialog/index.js.map +1 -0
  416. package/dist/primitives/folder-context-card/index.js +4 -0
  417. package/dist/primitives/folder-context-card/index.js.map +1 -0
  418. package/dist/primitives/folder-selector/index.js +4 -0
  419. package/dist/primitives/folder-selector/index.js.map +1 -0
  420. package/dist/primitives/form-field/index.js +4 -0
  421. package/dist/primitives/form-field/index.js.map +1 -0
  422. package/dist/primitives/gateway-status-indicator/index.js +4 -0
  423. package/dist/primitives/gateway-status-indicator/index.js.map +1 -0
  424. package/dist/primitives/hook-config/index.js +4 -0
  425. package/dist/primitives/hook-config/index.js.map +1 -0
  426. package/dist/primitives/hook-event-log/index.js +4 -0
  427. package/dist/primitives/hook-event-log/index.js.map +1 -0
  428. package/dist/primitives/input/index.js +4 -0
  429. package/dist/primitives/input/index.js.map +1 -0
  430. package/dist/primitives/intent-selector/index.js +4 -0
  431. package/dist/primitives/intent-selector/index.js.map +1 -0
  432. package/dist/primitives/label/index.js +4 -0
  433. package/dist/primitives/label/index.js.map +1 -0
  434. package/dist/primitives/lane-board/index.js +4 -0
  435. package/dist/primitives/lane-board/index.js.map +1 -0
  436. package/dist/primitives/login-split/index.js +4 -0
  437. package/dist/primitives/login-split/index.js.map +1 -0
  438. package/dist/primitives/mcp-server-card/index.js +4 -0
  439. package/dist/primitives/mcp-server-card/index.js.map +1 -0
  440. package/dist/primitives/memory-editor/index.js +4 -0
  441. package/dist/primitives/memory-editor/index.js.map +1 -0
  442. package/dist/primitives/mention-menu/index.js +4 -0
  443. package/dist/primitives/mention-menu/index.js.map +1 -0
  444. package/dist/primitives/metrics-panel/index.js +4 -0
  445. package/dist/primitives/metrics-panel/index.js.map +1 -0
  446. package/dist/primitives/model-card/index.js +4 -0
  447. package/dist/primitives/model-card/index.js.map +1 -0
  448. package/dist/primitives/model-selector/index.js +4 -0
  449. package/dist/primitives/model-selector/index.js.map +1 -0
  450. package/dist/primitives/pagination/index.js +4 -0
  451. package/dist/primitives/pagination/index.js.map +1 -0
  452. package/dist/primitives/permission-matrix/index.js +4 -0
  453. package/dist/primitives/permission-matrix/index.js.map +1 -0
  454. package/dist/primitives/pin-input/index.js +4 -0
  455. package/dist/primitives/pin-input/index.js.map +1 -0
  456. package/dist/primitives/plan-badge/index.js +4 -0
  457. package/dist/primitives/plan-badge/index.js.map +1 -0
  458. package/dist/primitives/progress/index.js +4 -0
  459. package/dist/primitives/progress/index.js.map +1 -0
  460. package/dist/primitives/progress-checklist/index.js +4 -0
  461. package/dist/primitives/progress-checklist/index.js.map +1 -0
  462. package/dist/primitives/project-switcher/index.js +4 -0
  463. package/dist/primitives/project-switcher/index.js.map +1 -0
  464. package/dist/primitives/quick-action-chips/index.js +4 -0
  465. package/dist/primitives/quick-action-chips/index.js.map +1 -0
  466. package/dist/primitives/radio-group/index.js +4 -0
  467. package/dist/primitives/radio-group/index.js.map +1 -0
  468. package/dist/primitives/recent-folders-list/index.js +4 -0
  469. package/dist/primitives/recent-folders-list/index.js.map +1 -0
  470. package/dist/primitives/rule-card/index.js +4 -0
  471. package/dist/primitives/rule-card/index.js.map +1 -0
  472. package/dist/primitives/run-stats/index.js +4 -0
  473. package/dist/primitives/run-stats/index.js.map +1 -0
  474. package/dist/primitives/run-status-pill/index.js +4 -0
  475. package/dist/primitives/run-status-pill/index.js.map +1 -0
  476. package/dist/primitives/running-tasks-panel/index.js +4 -0
  477. package/dist/primitives/running-tasks-panel/index.js.map +1 -0
  478. package/dist/primitives/scroll-area/index.js +4 -0
  479. package/dist/primitives/scroll-area/index.js.map +1 -0
  480. package/dist/primitives/select/index.js +4 -0
  481. package/dist/primitives/select/index.js.map +1 -0
  482. package/dist/primitives/session-list-item/index.js +4 -0
  483. package/dist/primitives/session-list-item/index.js.map +1 -0
  484. package/dist/primitives/session-timeline/index.js +4 -0
  485. package/dist/primitives/session-timeline/index.js.map +1 -0
  486. package/dist/primitives/sheet/index.js +4 -0
  487. package/dist/primitives/sheet/index.js.map +1 -0
  488. package/dist/primitives/sidebar/index.js +4 -0
  489. package/dist/primitives/sidebar/index.js.map +1 -0
  490. package/dist/primitives/skeleton/index.js +5 -0
  491. package/dist/primitives/skeleton/index.js.map +1 -0
  492. package/dist/primitives/skill-card/index.js +4 -0
  493. package/dist/primitives/skill-card/index.js.map +1 -0
  494. package/dist/primitives/social-auth-row/index.js +4 -0
  495. package/dist/primitives/social-auth-row/index.js.map +1 -0
  496. package/dist/primitives/stat-tile/index.js +4 -0
  497. package/dist/primitives/stat-tile/index.js.map +1 -0
  498. package/dist/primitives/status-dot/index.js +4 -0
  499. package/dist/primitives/status-dot/index.js.map +1 -0
  500. package/dist/primitives/steps-rail/index.js +4 -0
  501. package/dist/primitives/steps-rail/index.js.map +1 -0
  502. package/dist/primitives/sub-agent-dispatch/index.js +4 -0
  503. package/dist/primitives/sub-agent-dispatch/index.js.map +1 -0
  504. package/dist/primitives/switch/index.js +4 -0
  505. package/dist/primitives/switch/index.js.map +1 -0
  506. package/dist/primitives/system-prompt-editor/index.js +4 -0
  507. package/dist/primitives/system-prompt-editor/index.js.map +1 -0
  508. package/dist/primitives/table/index.js +4 -0
  509. package/dist/primitives/table/index.js.map +1 -0
  510. package/dist/primitives/tabs/index.js +4 -0
  511. package/dist/primitives/tabs/index.js.map +1 -0
  512. package/dist/primitives/task-plan/index.js +4 -0
  513. package/dist/primitives/task-plan/index.js.map +1 -0
  514. package/dist/primitives/terminal-panel/index.js +5 -0
  515. package/dist/primitives/terminal-panel/index.js.map +1 -0
  516. package/dist/primitives/textarea/index.js +4 -0
  517. package/dist/primitives/textarea/index.js.map +1 -0
  518. package/dist/primitives/thinking-level-selector/index.js +4 -0
  519. package/dist/primitives/thinking-level-selector/index.js.map +1 -0
  520. package/dist/primitives/timestamp/index.js +4 -0
  521. package/dist/primitives/timestamp/index.js.map +1 -0
  522. package/dist/primitives/toast/index.js +4 -0
  523. package/dist/primitives/toast/index.js.map +1 -0
  524. package/dist/primitives/token-usage-chart/index.js +4 -0
  525. package/dist/primitives/token-usage-chart/index.js.map +1 -0
  526. package/dist/primitives/tool-call/index.js +4 -0
  527. package/dist/primitives/tool-call/index.js.map +1 -0
  528. package/dist/primitives/tool-call-card/index.js +4 -0
  529. package/dist/primitives/tool-call-card/index.js.map +1 -0
  530. package/dist/primitives/tool-result/index.js +4 -0
  531. package/dist/primitives/tool-result/index.js.map +1 -0
  532. package/dist/primitives/tools-list/index.js +4 -0
  533. package/dist/primitives/tools-list/index.js.map +1 -0
  534. package/dist/primitives/tooltip/index.js +4 -0
  535. package/dist/primitives/tooltip/index.js.map +1 -0
  536. package/dist/primitives/topnav/index.js +4 -0
  537. package/dist/primitives/topnav/index.js.map +1 -0
  538. package/dist/primitives/update-banner/index.js +4 -0
  539. package/dist/primitives/update-banner/index.js.map +1 -0
  540. package/dist/slide/index.d.ts +212 -0
  541. package/dist/slide/index.js +3 -0
  542. package/dist/slide/index.js.map +1 -0
  543. package/dist/slide/plugins/emoji/index.d.ts +29 -0
  544. package/dist/slide/plugins/emoji/index.js +157 -0
  545. package/dist/slide/plugins/emoji/index.js.map +1 -0
  546. package/dist/slide/plugins/math/index.d.ts +13 -0
  547. package/dist/slide/plugins/math/index.js +145 -0
  548. package/dist/slide/plugins/math/index.js.map +1 -0
  549. package/dist/slide/plugins/mermaid/index.d.ts +55 -0
  550. package/dist/slide/plugins/mermaid/index.js +218 -0
  551. package/dist/slide/plugins/mermaid/index.js.map +1 -0
  552. package/dist/slide/plugins/shiki/index.d.ts +18 -0
  553. package/dist/slide/plugins/shiki/index.js +87 -0
  554. package/dist/slide/plugins/shiki/index.js.map +1 -0
  555. package/dist/slide/themes/default.css +256 -0
  556. package/dist/slide/themes/layouts.css +143 -0
  557. package/dist/slide/themes/violet-forge.css +256 -0
  558. package/dist/slide-deck/index.css +52 -0
  559. package/dist/slide-deck/index.css.map +1 -0
  560. package/dist/slide-deck/index.d.ts +377 -0
  561. package/dist/slide-deck/index.js +1111 -0
  562. package/dist/slide-deck/index.js.map +1 -0
  563. package/dist/styles-v3-legacy.css +88 -0
  564. package/dist/styles.css +137 -0
  565. package/dist/tokens-v4.css +187 -0
  566. package/dist/tokens.css +230 -0
  567. package/dist/vite-plugin.d.ts +29 -0
  568. package/dist/vite-plugin.js +76 -0
  569. package/dist/vite-plugin.js.map +1 -0
  570. package/dist/whiteboard/index.d.ts +258 -0
  571. package/dist/whiteboard/index.js +738 -0
  572. package/dist/whiteboard/index.js.map +1 -0
  573. package/llms.txt +273 -0
  574. package/package.json +800 -0
  575. package/registry/index.json +856 -0
  576. package/registry/r/account-menu.json +24 -0
  577. package/registry/r/action-bar.json +22 -0
  578. package/registry/r/agent-composer.json +22 -0
  579. package/registry/r/agent-editor.json +27 -0
  580. package/registry/r/agent-error-card.json +22 -0
  581. package/registry/r/agent-event.json +24 -0
  582. package/registry/r/agent-handoff.json +22 -0
  583. package/registry/r/agent-profile.json +23 -0
  584. package/registry/r/agent-starting-state.json +22 -0
  585. package/registry/r/agent-stream.json +27 -0
  586. package/registry/r/agent-streaming.json +22 -0
  587. package/registry/r/agent-timeline.json +22 -0
  588. package/registry/r/agent-types.json +15 -0
  589. package/registry/r/alert.json +22 -0
  590. package/registry/r/approval-card.json +25 -0
  591. package/registry/r/artifact-preview.json +22 -0
  592. package/registry/r/attachment-chip.json +24 -0
  593. package/registry/r/audit-log-entry.json +23 -0
  594. package/registry/r/auto-compact-notice.json +22 -0
  595. package/registry/r/avatar.json +23 -0
  596. package/registry/r/badge.json +22 -0
  597. package/registry/r/browser-controls.json +22 -0
  598. package/registry/r/build-log-stream.json +19 -0
  599. package/registry/r/button.json +23 -0
  600. package/registry/r/capability-indicator.json +23 -0
  601. package/registry/r/card.json +22 -0
  602. package/registry/r/chat-composer.json +23 -0
  603. package/registry/r/chat-message.json +129 -0
  604. package/registry/r/chat-thread.json +20 -0
  605. package/registry/r/chat-types.json +15 -0
  606. package/registry/r/checkbox.json +24 -0
  607. package/registry/r/cn.json +19 -0
  608. package/registry/r/code-block.json +21 -0
  609. package/registry/r/command-palette.json +25 -0
  610. package/registry/r/confirm-dialog.json +25 -0
  611. package/registry/r/context-card.json +23 -0
  612. package/registry/r/context-window-bar.json +20 -0
  613. package/registry/r/copy-button.json +22 -0
  614. package/registry/r/cost-meter.json +22 -0
  615. package/registry/r/created-files-card.json +23 -0
  616. package/registry/r/cron-job-card.json +22 -0
  617. package/registry/r/cron-jobs-list.json +23 -0
  618. package/registry/r/danger-zone.json +20 -0
  619. package/registry/r/data-table.json +27 -0
  620. package/registry/r/deployment-row.json +23 -0
  621. package/registry/r/dialog.json +23 -0
  622. package/registry/r/diff-viewer.json +20 -0
  623. package/registry/r/domain-config.json +25 -0
  624. package/registry/r/dropdown-menu.json +23 -0
  625. package/registry/r/empty-state.json +20 -0
  626. package/registry/r/env-var-editor.json +25 -0
  627. package/registry/r/folder-context-card.json +23 -0
  628. package/registry/r/folder-selector.json +22 -0
  629. package/registry/r/form-field.json +23 -0
  630. package/registry/r/hook-config.json +22 -0
  631. package/registry/r/hook-event-log.json +22 -0
  632. package/registry/r/input.json +22 -0
  633. package/registry/r/intent-selector.json +24 -0
  634. package/registry/r/label.json +22 -0
  635. package/registry/r/lane-board.json +20 -0
  636. package/registry/r/live-region-context.json +16 -0
  637. package/registry/r/login-split.json +20 -0
  638. package/registry/r/mcp-server-card.json +22 -0
  639. package/registry/r/mcp-server-list.json +23 -0
  640. package/registry/r/memory-editor.json +23 -0
  641. package/registry/r/mention-menu.json +23 -0
  642. package/registry/r/metrics-panel.json +22 -0
  643. package/registry/r/mode-types.json +15 -0
  644. package/registry/r/model-card.json +23 -0
  645. package/registry/r/model-selector.json +23 -0
  646. package/registry/r/page-shell.json +25 -0
  647. package/registry/r/pagination.json +22 -0
  648. package/registry/r/permission-matrix.json +22 -0
  649. package/registry/r/permission-modal.json +24 -0
  650. package/registry/r/permission-types.json +15 -0
  651. package/registry/r/pin-input.json +20 -0
  652. package/registry/r/plan-badge.json +20 -0
  653. package/registry/r/preview-env-card.json +25 -0
  654. package/registry/r/preview-panel.json +21 -0
  655. package/registry/r/progress-checklist.json +23 -0
  656. package/registry/r/progress.json +20 -0
  657. package/registry/r/project-card.json +25 -0
  658. package/registry/r/project-switcher.json +22 -0
  659. package/registry/r/quick-action-chips.json +21 -0
  660. package/registry/r/radio-group.json +23 -0
  661. package/registry/r/recent-folders-list.json +22 -0
  662. package/registry/r/rollback-ui.json +24 -0
  663. package/registry/r/rule-card.json +23 -0
  664. package/registry/r/rule-editor.json +28 -0
  665. package/registry/r/rule-types.json +18 -0
  666. package/registry/r/run-stats.json +22 -0
  667. package/registry/r/running-tasks-panel.json +22 -0
  668. package/registry/r/safe-href.json +16 -0
  669. package/registry/r/scroll-area.json +22 -0
  670. package/registry/r/select.json +24 -0
  671. package/registry/r/session-list-item.json +20 -0
  672. package/registry/r/session-timeline.json +22 -0
  673. package/registry/r/sheet.json +24 -0
  674. package/registry/r/sidebar.json +19 -0
  675. package/registry/r/skeleton.json +19 -0
  676. package/registry/r/skill-card.json +24 -0
  677. package/registry/r/skill-editor.json +28 -0
  678. package/registry/r/skills-list.json +23 -0
  679. package/registry/r/slide-deck.json +130 -0
  680. package/registry/r/slide-plugin-emoji.json +28 -0
  681. package/registry/r/slide-plugin-math.json +24 -0
  682. package/registry/r/slide-plugin-mermaid.json +23 -0
  683. package/registry/r/slide-plugin-shiki.json +23 -0
  684. package/registry/r/slide.json +123 -0
  685. package/registry/r/social-auth-row.json +21 -0
  686. package/registry/r/stat-tile.json +22 -0
  687. package/registry/r/status-dot.json +20 -0
  688. package/registry/r/steps-rail.json +20 -0
  689. package/registry/r/sub-agent-dispatch.json +22 -0
  690. package/registry/r/switch.json +23 -0
  691. package/registry/r/system-prompt-editor.json +22 -0
  692. package/registry/r/table.json +22 -0
  693. package/registry/r/tabs.json +22 -0
  694. package/registry/r/tailwind-preset.json +19 -0
  695. package/registry/r/task-header.json +24 -0
  696. package/registry/r/task-plan.json +22 -0
  697. package/registry/r/task-types.json +15 -0
  698. package/registry/r/terminal-panel.json +22 -0
  699. package/registry/r/textarea.json +22 -0
  700. package/registry/r/theme-provider.json +59 -0
  701. package/registry/r/theme-script.json +18 -0
  702. package/registry/r/theo-ui-provider.json +20 -0
  703. package/registry/r/timestamp.json +20 -0
  704. package/registry/r/toast.json +30 -0
  705. package/registry/r/token-usage-chart.json +20 -0
  706. package/registry/r/tokens.json +21 -0
  707. package/registry/r/tool-call-card.json +23 -0
  708. package/registry/r/tool-call.json +22 -0
  709. package/registry/r/tool-result.json +20 -0
  710. package/registry/r/tools-list.json +23 -0
  711. package/registry/r/tooltip.json +22 -0
  712. package/registry/r/topnav.json +22 -0
  713. package/registry/r/types.json +15 -0
  714. package/registry/r/usage-meter.json +21 -0
  715. package/registry/r/whiteboard.json +101 -0
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "permission-matrix",
4
+ "type": "registry:ui",
5
+ "title": "PermissionMatrix",
6
+ "description": "Tool × path × decision grid for fine-grained access",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
12
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "components/primitives/permission-matrix/permission-matrix.tsx",
17
+ "type": "registry:ui",
18
+ "target": "components/ui/permission-matrix.tsx",
19
+ "content": "import { Check, Lock, Plus, ShieldQuestion, Trash2 } from \"lucide-react\";\nimport { forwardRef, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\nexport type PermissionDecisionKind = \"allow\" | \"ask\" | \"deny\";\n\nexport interface PermissionRule {\n id: string;\n /** Tool the rule applies to. Use \"*\" for any. */\n tool: string;\n /** Glob path it applies to. Use \"*\" for any. */\n path: string;\n decision: PermissionDecisionKind;\n /** Optional rationale shown as helper text. */\n note?: string;\n}\n\ninterface PermissionMatrixProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n rules: PermissionRule[];\n title?: ReactNode;\n /**\n * Available tools shown in the add form. Pass `undefined` (or omit) — or an\n * empty array — to hide the add form entirely. The form only renders when\n * `onAdd` is provided AND `toolOptions` has at least one entry.\n */\n toolOptions?: string[];\n onAdd?: (rule: Omit<PermissionRule, \"id\">) => void;\n onRemove?: (id: string) => void;\n onDecisionChange?: (id: string, decision: PermissionDecisionKind) => void;\n}\n\nconst DECISION_CLASS: Record<PermissionDecisionKind, string> = {\n allow: \"bg-success/15 text-success border-success/40\",\n ask: \"bg-warning/15 text-warning border-warning/40\",\n deny: \"bg-destructive/15 text-destructive border-destructive/40\",\n};\n\nconst DECISION_ICON: Record<PermissionDecisionKind, ReactNode> = {\n allow: <Check className=\"size-3\" aria-hidden=\"true\" />,\n ask: <ShieldQuestion className=\"size-3\" aria-hidden=\"true\" />,\n deny: <Lock className=\"size-3\" aria-hidden=\"true\" />,\n};\n\nconst cycle = (cur: PermissionDecisionKind): PermissionDecisionKind =>\n cur === \"allow\" ? \"ask\" : cur === \"ask\" ? \"deny\" : \"allow\";\n\n/**\n * PermissionMatrix — tool × path × decision grid for fine-grained access\n * control. Used as the \"permissions\" tab in the agent settings.\n *\n * One PermissionRule per row. Click the decision pill to cycle Allow → Ask → Deny.\n *\n * Design decision (2026-05-14): PermissionMatrix stays in `primitives/`\n * — not `composites/` — even though it renders inputs and a select. The native\n * `<input>` / `<select>` elements use Theo design tokens directly (border-input,\n * ring, font-mono) so visual parity with `Input` / `Select` primitives is\n * preserved. Reason for keeping it primitive: a consumer installing\n * `permission-matrix` from the registry gets a single self-contained file with\n * no transitive Theo dependencies — opposite trade-off from `EnvVarEditor`\n * which is intentionally a composite. Both shapes are valid; we ship one of\n * each so consumers can pick the dependency profile that fits their app.\n */\nconst PermissionMatrix = forwardRef<HTMLDivElement, PermissionMatrixProps>(\n (\n {\n className,\n rules,\n title = \"Permissions\",\n toolOptions,\n onAdd,\n onRemove,\n onDecisionChange,\n ...props\n },\n ref,\n ) => {\n const [newTool, setNewTool] = useState(toolOptions?.[0] ?? \"*\");\n const [newPath, setNewPath] = useState(\"\");\n const [newDecision, setNewDecision] = useState<PermissionDecisionKind>(\"ask\");\n\n const submit = () => {\n if (!newPath.trim()) return;\n onAdd?.({ tool: newTool, path: newPath.trim(), decision: newDecision });\n setNewPath(\"\");\n };\n\n return (\n <section ref={ref} className={cn(\"rounded-xl border bg-card\", className)} {...props}>\n {title ? (\n <header className=\"flex items-baseline justify-between border-border/40 border-b px-4 py-3\">\n <h3 className=\"font-display text-title-md tracking-tight\">{title}</h3>\n <span className=\"font-mono text-label text-muted-foreground\">\n {rules.length} {rules.length === 1 ? \"rule\" : \"rules\"}\n </span>\n </header>\n ) : null}\n\n {onAdd && toolOptions && toolOptions.length > 0 ? (\n <form\n className=\"grid grid-cols-[1fr_2fr_auto_auto] gap-2 border-border/40 border-b p-3\"\n onSubmit={(e) => {\n e.preventDefault();\n submit();\n }}\n >\n <select\n value={newTool}\n onChange={(e) => setNewTool(e.target.value)}\n aria-label=\"Tool\"\n className=\"h-9 rounded-md border border-input bg-card px-2 font-mono text-code-sm\"\n >\n <option value=\"*\">* (any tool)</option>\n {toolOptions.map((t) => (\n <option key={t} value={t}>\n {t}\n </option>\n ))}\n </select>\n <input\n type=\"text\"\n value={newPath}\n onChange={(e) => setNewPath(e.target.value)}\n placeholder=\"path glob (e.g. src/**/*.ts)\"\n aria-label=\"Path\"\n className=\"h-9 rounded-md border border-input bg-card px-2 font-mono text-code-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n <select\n value={newDecision}\n onChange={(e) => setNewDecision(e.target.value as PermissionDecisionKind)}\n aria-label=\"Decision\"\n className=\"h-9 rounded-md border border-input bg-card px-2 font-mono text-code-sm uppercase\"\n >\n <option value=\"allow\">allow</option>\n <option value=\"ask\">ask</option>\n <option value=\"deny\">deny</option>\n </select>\n <button\n type=\"submit\"\n className=\"inline-flex h-9 items-center gap-1 rounded-md bg-primary px-3 font-sans text-label text-primary-foreground hover:shadow-glow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <Plus className=\"size-3.5\" /> Add\n </button>\n </form>\n ) : null}\n\n <ul className=\"divide-y divide-border/30\">\n {rules.map((rule) => (\n <li\n key={rule.id}\n className=\"grid grid-cols-[1fr_2fr_auto_auto] items-center gap-3 px-4 py-2.5\"\n >\n <span className=\"truncate font-mono text-code-sm text-foreground\">{rule.tool}</span>\n <span className=\"truncate font-mono text-code-sm text-muted-foreground\">\n {rule.path}\n </span>\n <button\n type=\"button\"\n onClick={() => onDecisionChange?.(rule.id, cycle(rule.decision))}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1\",\n \"font-mono text-label uppercase tracking-wider transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n DECISION_CLASS[rule.decision],\n !onDecisionChange && \"pointer-events-none\",\n )}\n >\n {DECISION_ICON[rule.decision]}\n {rule.decision}\n </button>\n {onRemove ? (\n <button\n type=\"button\"\n onClick={() => onRemove(rule.id)}\n aria-label={`Remove rule ${rule.tool} ${rule.path}`}\n className=\"rounded-md p-1.5 text-muted-foreground hover:bg-muted hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <Trash2 className=\"size-3.5\" />\n </button>\n ) : null}\n </li>\n ))}\n {rules.length === 0 ? (\n <li className=\"px-4 py-8 text-center font-sans text-body-sm text-muted-foreground\">\n No permission rules configured. The agent will fall back to default policy.\n </li>\n ) : null}\n </ul>\n </section>\n );\n },\n);\nPermissionMatrix.displayName = \"PermissionMatrix\";\n\nexport { PermissionMatrix };\n"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "permission-modal",
4
+ "type": "registry:ui",
5
+ "title": "PermissionModal",
6
+ "description": "Local-files access prompt built on Dialog.",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://usetheodev.github.io/theo-ui/r/button.json",
12
+ "https://usetheodev.github.io/theo-ui/r/dialog.json",
13
+ "https://usetheodev.github.io/theo-ui/r/permission-types.json",
14
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "components/composites/permission-modal/permission-modal.tsx",
19
+ "type": "registry:ui",
20
+ "target": "components/ui/permission-modal.tsx",
21
+ "content": "import { AlertTriangle, FolderOpen, ShieldAlert } from \"lucide-react\";\nimport { useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type {\n PermissionDecision,\n PermissionOperation,\n PermissionRequest,\n} from \"@/types/permission\";\nimport { Button } from \"@/components/ui/button\";\nimport { Dialog } from \"@/components/ui/dialog\";\n\n/**\n * Friendly operation labels used by the default copy. Override with the\n * `operationLabels` prop to localize or rephrase per project.\n */\nexport const defaultOperationLabels: Record<PermissionOperation, string> = {\n read: \"read\",\n write: \"edit\",\n delete: \"permanently delete\",\n};\n\ninterface PermissionModalLabels {\n /** \"Cancel\" button. */\n cancel: ReactNode;\n /** \"Always allow\" tertiary button. */\n always: ReactNode;\n /** \"Allow once\" primary button. */\n allow: ReactNode;\n /** Inline label rendered before the operation list inside the body card. */\n requestedOps: ReactNode;\n}\n\nconst defaultLabels: PermissionModalLabels = {\n cancel: \"Cancel\",\n always: \"Always allow\",\n allow: \"Allow once\",\n requestedOps: \"Requested operations:\",\n};\n\ninterface PermissionModalProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n request: PermissionRequest;\n /**\n * Fires when the user picks a decision. The modal does NOT auto-close;\n * caller decides whether the decision should dismiss the modal.\n */\n onDecide: (decision: PermissionDecision) => void;\n /** Override the modal title. Defaults to \"Allow Theo to {ops} files in {path}?\". */\n title?: ReactNode;\n /** Override the modal description (body lead text). */\n description?: ReactNode;\n /** Override the verb used for each operation in the default copy. */\n operationLabels?: Partial<Record<PermissionOperation, string>>;\n /** Override button text + inline labels. Useful for i18n. */\n labels?: Partial<PermissionModalLabels>;\n}\n\n/**\n * PermissionModal — local-files access prompt built on Dialog.\n *\n * Three actions: Cancel (denied), Always allow, Allow once. Per WIREMOCKS §5,\n * the path is shown in the title (not hidden in body) and destructive\n * operations are listed inline.\n *\n * All visible text can be overridden via `title`, `description`,\n * `operationLabels`, and `labels`. Defaults are English; pass overrides for\n * other locales.\n */\nfunction PermissionModal({\n open,\n onOpenChange,\n request,\n onDecide,\n title,\n description,\n operationLabels,\n labels,\n}: PermissionModalProps) {\n const opLabels = { ...defaultOperationLabels, ...operationLabels };\n const opsList = request.operations.map((op) => opLabels[op]).join(\", \");\n const text = { ...defaultLabels, ...labels };\n\n // T4.4 (Code Issue 4): Esc / overlay-click previously fired onOpenChange(false)\n // but never onDecide — users saw \"Cancel\" semantics, app saw silent dismissal.\n // Track whether an explicit button decision happened; if the dialog closes\n // without one, treat it as denied. decidedRef must reset on every fresh open\n // so a rapid close-then-open doesn't carry state forward.\n const decidedRef = useRef(false);\n function handleDecide(decision: PermissionDecision) {\n decidedRef.current = true;\n onDecide(decision);\n }\n function handleOpenChange(next: boolean) {\n const wasDecided = decidedRef.current;\n // Reset BEFORE invoking onDecide so a re-open within the same tick starts\n // clean. Edge case from SF-6: rapid toggle could leave decidedRef=true.\n decidedRef.current = false;\n if (!next && !wasDecided) {\n onDecide(\"denied\");\n }\n onOpenChange(next);\n }\n\n const defaultTitle = (\n <span className=\"flex items-center gap-2\">\n <ShieldAlert className=\"size-5 text-warning\" aria-hidden=\"true\" />\n Allow Theo to {opsList} files in{\" \"}\n <code className=\"rounded-md bg-muted px-1.5 py-0.5 font-mono text-code-md text-primary\">\n {request.path}\n </code>\n ?\n </span>\n );\n\n const defaultDescription = (\n <>\n This includes all files and subfolders. Theo will be able to {opsList} and may share the\n contents with connected third-party tools. Be careful when exposing confidential information.\n </>\n );\n\n return (\n <Dialog open={open} onOpenChange={handleOpenChange}>\n <Dialog.Content className=\"max-w-xl\">\n <Dialog.Header>\n <Dialog.Title>{title ?? defaultTitle}</Dialog.Title>\n <Dialog.Description>{description ?? defaultDescription}</Dialog.Description>\n </Dialog.Header>\n <Dialog.Body>\n <div className=\"flex items-start gap-3 rounded-md border border-border/40 bg-muted/40 p-3\">\n <FolderOpen\n className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n <div className=\"grid gap-1\">\n <p className=\"font-mono text-code-sm text-foreground\">{request.path}</p>\n <p className=\"flex items-center gap-1.5 font-sans text-label text-warning\">\n <AlertTriangle className=\"size-3\" aria-hidden=\"true\" />\n {text.requestedOps} {opsList}\n </p>\n </div>\n </div>\n </Dialog.Body>\n <Dialog.Footer>\n <Button variant=\"secondary\" onClick={() => handleDecide(\"denied\")}>\n {text.cancel}\n </Button>\n <Button variant=\"ghost\" onClick={() => handleDecide(\"always_allowed\")}>\n {text.always}\n </Button>\n <Button onClick={() => handleDecide(\"allowed_once\")}>{text.allow}</Button>\n </Dialog.Footer>\n </Dialog.Content>\n </Dialog>\n );\n}\n\nexport { PermissionModal };\n"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "permission-types",
4
+ "type": "registry:lib",
5
+ "title": "Theo UI permission types",
6
+ "description": "Shared TypeScript types for permission requests, scopes, and decisions.",
7
+ "files": [
8
+ {
9
+ "path": "types/permission.ts",
10
+ "type": "registry:lib",
11
+ "target": "types/permission.ts",
12
+ "content": "export type PermissionOperation = \"read\" | \"write\" | \"delete\";\nexport type PermissionDecision = \"denied\" | \"allowed_once\" | \"always_allowed\";\n\nexport interface PermissionRequest {\n /** Absolute path to the resource (file or folder). */\n path: string;\n /** Operations the agent wants to perform on the resource. */\n operations: PermissionOperation[];\n}\n"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "pin-input",
4
+ "type": "registry:ui",
5
+ "title": "PinInput",
6
+ "description": "Multi-slot OTP / code input primitive. N separate boxes (default 6) that auto-advance focus on input. Paste fills all slots from clipboard with whitespace stripped. Arrow keys navigate; backspace clears current slot then moves focus back when empty. numeric / alphanumeric inputMode (default numeric, triggers mobile numeric keyboard via pattern=[0-9]*). Optional mask renders bullets. Optional error state applies destructive border. onComplete fires once when value reaches length — NOT on mount with pre-filled value.",
7
+ "dependencies": [],
8
+ "registryDependencies": [
9
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
10
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
11
+ ],
12
+ "files": [
13
+ {
14
+ "path": "components/primitives/pin-input/pin-input.tsx",
15
+ "type": "registry:ui",
16
+ "target": "components/ui/pin-input.tsx",
17
+ "content": "import { forwardRef, useEffect, useRef } from \"react\";\nimport type { ClipboardEvent, HTMLAttributes, KeyboardEvent } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * PinInput — multi-slot OTP / code input primitive.\n *\n * Renders N separate boxes (default 6) that auto-advance focus on\n * input. Paste handling fills all slots from clipboard (whitespace\n * stripped). Arrow keys navigate; backspace clears current slot\n * then moves focus back when empty.\n *\n * Industry-standard pattern for email verification codes (Apple,\n * Stripe, Clerk, Auth0, GitHub two-factor).\n *\n * @example\n * <PinInput\n * length={6}\n * value={code}\n * onChange={setCode}\n * onComplete={(v) => verify(v)}\n * inputMode=\"numeric\"\n * aria-label=\"Verification code\"\n * />\n *\n * Note: value is treated as controlled. If you pass a complete value\n * on mount, onComplete will NOT fire — onComplete fires only on\n * transitions from incomplete → complete.\n */\nexport interface PinInputProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\" | \"inputMode\"> {\n length?: number;\n value?: string;\n onChange?: (value: string) => void;\n onComplete?: (value: string) => void;\n inputMode?: \"numeric\" | \"alphanumeric\";\n size?: \"sm\" | \"md\" | \"lg\";\n disabled?: boolean;\n error?: boolean;\n \"aria-label\": string;\n autoFocus?: boolean;\n mask?: boolean;\n}\n\nconst SIZE_CLASS: Record<NonNullable<PinInputProps[\"size\"]>, string> = {\n sm: \"size-8 text-body-sm\",\n md: \"size-10 text-body-md\",\n lg: \"size-12 text-title-sm\",\n};\n\nfunction sanitize(raw: string, inputMode: \"numeric\" | \"alphanumeric\"): string {\n const noWhitespace = raw.replace(/\\s/g, \"\");\n if (inputMode === \"numeric\") {\n return noWhitespace.replace(/\\D/g, \"\");\n }\n return noWhitespace.toUpperCase().replace(/[^A-Z0-9]/g, \"\");\n}\n\nconst PinInput = forwardRef<HTMLDivElement, PinInputProps>(\n (\n {\n className,\n length = 6,\n value = \"\",\n onChange,\n onComplete,\n inputMode = \"numeric\",\n size = \"md\",\n disabled = false,\n error = false,\n autoFocus = false,\n mask = false,\n \"aria-label\": ariaLabel,\n ...props\n },\n ref,\n ) => {\n const inputRefs = useRef<Array<HTMLInputElement | null>>([]);\n const wasCompleteRef = useRef<boolean>(value.length === length);\n\n // Auto-focus first slot on mount (SSR-safe)\n useEffect(() => {\n if (!autoFocus) return;\n if (typeof window === \"undefined\") return;\n inputRefs.current[0]?.focus();\n }, [autoFocus]);\n\n // Fire onComplete on transitions from incomplete → complete\n useEffect(() => {\n const isComplete = value.length === length && value.length > 0;\n if (isComplete && !wasCompleteRef.current) {\n onComplete?.(value);\n }\n wasCompleteRef.current = isComplete;\n }, [value, length, onComplete]);\n\n function commit(next: string) {\n const sanitized = sanitize(next, inputMode).slice(0, length);\n onChange?.(sanitized);\n }\n\n function handleChange(slot: number, raw: string) {\n const sanitized = sanitize(raw, inputMode);\n if (sanitized.length === 0) {\n // Clear current slot\n const next = `${value.slice(0, slot)}${value.slice(slot + 1)}`;\n commit(next);\n return;\n }\n // Take the last character typed (handles browser autocomplete that fills multiple)\n const ch = sanitized[sanitized.length - 1] ?? \"\";\n const next = `${value.slice(0, slot)}${ch}${value.slice(slot + 1)}`;\n commit(next);\n // Advance focus\n if (slot < length - 1) {\n inputRefs.current[slot + 1]?.focus();\n }\n }\n\n function handleKeyDown(slot: number, e: KeyboardEvent<HTMLInputElement>) {\n if (disabled) return;\n const slotChar = value[slot] ?? \"\";\n\n if (e.key === \"Backspace\") {\n if (slotChar === \"\") {\n // Move focus back if current is empty\n if (slot > 0) {\n inputRefs.current[slot - 1]?.focus();\n }\n } else {\n // Clear current slot, stay focused\n const next = `${value.slice(0, slot)}${value.slice(slot + 1)}`;\n commit(next);\n }\n e.preventDefault();\n } else if (e.key === \"ArrowLeft\") {\n if (slot > 0) inputRefs.current[slot - 1]?.focus();\n e.preventDefault();\n } else if (e.key === \"ArrowRight\") {\n if (slot < length - 1) inputRefs.current[slot + 1]?.focus();\n e.preventDefault();\n }\n }\n\n function handlePaste(slot: number, e: ClipboardEvent<HTMLInputElement>) {\n if (disabled) return;\n e.preventDefault();\n const pasted = e.clipboardData.getData(\"text/plain\");\n const sanitized = sanitize(pasted, inputMode);\n if (sanitized.length === 0) return;\n // Build slot-indexed array, then overwrite from `slot` onwards.\n // Previous string-concat approach didn't pad when value was shorter\n // than `slot`, which made paste-from-middle-when-empty fill from 0.\n const slotArr: string[] = Array.from({ length }, (_, i) => value[i] ?? \"\");\n const remaining = length - slot;\n const filled = sanitized.slice(0, remaining);\n for (let i = 0; i < filled.length; i++) {\n slotArr[slot + i] = filled[i] ?? \"\";\n }\n const next = slotArr.join(\"\");\n commit(next);\n // Focus the slot after the last filled, or the last slot if completed\n const focusAt = Math.min(slot + filled.length, length - 1);\n requestAnimationFrame(() => inputRefs.current[focusAt]?.focus());\n }\n\n const slots = Array.from({ length }, (_, i) => i);\n\n return (\n <div\n ref={ref}\n // biome-ignore lint/a11y/useSemanticElements: <fieldset> would force a different visual layout (rectangular border by default) and is form-bound; we use a div with role=\"group\" + aria-label for grouping semantics.\n role=\"group\"\n aria-label={ariaLabel}\n className={cn(\"inline-flex items-center gap-2\", className)}\n {...props}\n >\n {slots.map((i) => {\n const ch = value[i] ?? \"\";\n const display = mask && ch !== \"\" ? \"•\" : ch;\n return (\n <input\n key={i}\n ref={(el) => {\n inputRefs.current[i] = el;\n }}\n type=\"text\"\n inputMode={inputMode === \"numeric\" ? \"numeric\" : \"text\"}\n pattern={inputMode === \"numeric\" ? \"[0-9]*\" : undefined}\n maxLength={1}\n autoComplete={i === 0 ? \"one-time-code\" : \"off\"}\n disabled={disabled}\n value={display}\n onChange={(e) => handleChange(i, e.target.value)}\n onKeyDown={(e) => handleKeyDown(i, e)}\n onPaste={(e) => handlePaste(i, e)}\n aria-label={`Digit ${i + 1} of ${length}`}\n className={cn(\n \"rounded-md border bg-card text-center font-medium font-mono\",\n \"transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n SIZE_CLASS[size],\n error ? \"border-destructive\" : \"border-border/60 hover:border-border\",\n )}\n />\n );\n })}\n </div>\n );\n },\n);\nPinInput.displayName = \"PinInput\";\n\nexport { PinInput };\n"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "plan-badge",
4
+ "type": "registry:ui",
5
+ "title": "PlanBadge",
6
+ "description": "Semantic pricing-tier badge. Five canonical tiers (free, hobby, pro, team, enterprise) with distinct color tokens. Consumers self-document intent (plan=\"hobby\") instead of mapping generic Badge variants per app — future rebrand / dark-mode tweaks propagate automatically.",
7
+ "dependencies": [],
8
+ "registryDependencies": [
9
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
10
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
11
+ ],
12
+ "files": [
13
+ {
14
+ "path": "components/primitives/plan-badge/plan-badge.tsx",
15
+ "type": "registry:ui",
16
+ "target": "components/ui/plan-badge.tsx",
17
+ "content": "import { forwardRef } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * PlanBadge — semantic pricing-tier badge.\n *\n * Five canonical tiers (`free` / `hobby` / `pro` / `team` / `enterprise`) with\n * distinct color tokens. Consumers self-document intent with `plan=\"hobby\"`\n * instead of mapping a generic `<Badge variant=\"outline\">` to colors per app.\n * Future rebrand / dark-mode tweaks propagate automatically — no consumer\n * code change.\n *\n * Visual spec (per `theo/docs/handoff/2026-05-23-theo-ui-cloud-dashboard-gaps-brief.md`):\n *\n * | tier | bg | border | text |\n * |--------------|---------------------|--------------------------|-----------------------|\n * | free | bg-muted/40 | border-muted-foreground/20 | text-muted-foreground |\n * | hobby | bg-warning/10 | border-warning/30 | text-warning |\n * | pro | bg-primary/10 | border-primary/30 | text-primary |\n * | team | bg-success/10 | border-success/30 | text-success |\n * | enterprise | bg-foreground/5 | border-foreground/20 | text-foreground |\n *\n * Default label capitalizes the tier (`hobby → \"Hobby\"`, `enterprise → \"Enterprise\"`).\n *\n * Used by `<AccountMenu>` inline with the user name; usable standalone.\n */\n\nexport type PlanTier = \"free\" | \"hobby\" | \"pro\" | \"team\" | \"enterprise\";\n\nexport interface PlanBadgeProps extends HTMLAttributes<HTMLSpanElement> {\n /** Plan tier identifier. */\n plan: PlanTier;\n /** Override the display label. Defaults to the capitalized tier name. */\n label?: string;\n /** Size variant. */\n size?: \"sm\" | \"md\";\n}\n\nconst TIER_CLASS: Record<PlanTier, string> = {\n free: \"bg-muted/40 border-muted-foreground/20 text-muted-foreground\",\n hobby: \"bg-warning/10 border-warning/30 text-warning\",\n pro: \"bg-primary/10 border-primary/30 text-primary\",\n team: \"bg-success/10 border-success/30 text-success\",\n enterprise: \"bg-foreground/5 border-foreground/20 text-foreground\",\n};\n\nconst SIZE_CLASS = {\n sm: \"px-1.5 py-0 text-label-caps\",\n md: \"px-2 py-0.5 text-label\",\n} as const;\n\nfunction defaultLabel(plan: PlanTier): string {\n return plan.charAt(0).toUpperCase() + plan.slice(1);\n}\n\nconst PlanBadge = forwardRef<HTMLSpanElement, PlanBadgeProps>(\n ({ className, plan, label, size = \"md\", ...props }, ref) => {\n // Runtime fallback for unknown tier (TypeScript prevents this at compile\n // time; the guard handles consumers casting an arbitrary string).\n const tierClass = TIER_CLASS[plan] ?? TIER_CLASS.free;\n const displayLabel = label ?? defaultLabel(plan);\n return (\n <span\n ref={ref}\n className={cn(\n \"inline-flex items-center rounded-md border\",\n \"font-mono uppercase tabular-nums tracking-wider\",\n tierClass,\n SIZE_CLASS[size],\n className,\n )}\n data-plan={plan}\n {...props}\n >\n {displayLabel}\n </span>\n );\n },\n);\nPlanBadge.displayName = \"PlanBadge\";\n\nexport { PlanBadge };\n"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "preview-env-card",
4
+ "type": "registry:block",
5
+ "title": "PreviewEnvCard",
6
+ "description": "Preview environment card surfacing all services from one PR.",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://usetheodev.github.io/theo-ui/r/badge.json",
12
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
13
+ "https://usetheodev.github.io/theo-ui/r/deployment-row.json",
14
+ "https://usetheodev.github.io/theo-ui/r/safe-href.json",
15
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
16
+ ],
17
+ "files": [
18
+ {
19
+ "path": "components/composites/preview-env-card/preview-env-card.tsx",
20
+ "type": "registry:block",
21
+ "target": "components/blocks/preview-env-card.tsx",
22
+ "content": "import { ExternalLink, GitPullRequest, Server } from \"lucide-react\";\nimport { forwardRef } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport { safeHref } from \"@/lib/safe-href\";\nimport { Badge } from \"@/components/ui/badge\";\nimport type { DeploymentStatus } from \"@/components/blocks/deployment-row\";\n\nconst statusToVariant: Record<\n DeploymentStatus,\n \"default\" | \"primary\" | \"success\" | \"warning\" | \"destructive\"\n> = {\n queued: \"warning\",\n building: \"primary\",\n deploying: \"primary\",\n live: \"success\",\n failed: \"destructive\",\n cancelled: \"default\",\n};\nconst statusToDot: Record<\n DeploymentStatus,\n \"primary\" | \"success\" | \"warning\" | \"destructive\" | \"muted\"\n> = {\n queued: \"warning\",\n building: \"primary\",\n deploying: \"primary\",\n live: \"success\",\n failed: \"destructive\",\n cancelled: \"muted\",\n};\nconst statusLabels: Record<DeploymentStatus, string> = {\n queued: \"Queued\",\n building: \"Building\",\n deploying: \"Deploying\",\n live: \"Live\",\n failed: \"Failed\",\n cancelled: \"Cancelled\",\n};\n\nexport interface PreviewService {\n /** Service name e.g. \"api\", \"web\", \"worker\". */\n name: string;\n /** Live URL or null if not exposed (worker). */\n url?: string;\n status: DeploymentStatus;\n}\n\nexport interface PreviewEnv {\n id: string;\n prNumber: number;\n prTitle: string;\n branch: string;\n author?: { name: string; avatarUrl?: string };\n services: PreviewService[];\n createdAt: string;\n}\n\ninterface PreviewEnvCardProps extends HTMLAttributes<HTMLDivElement> {\n env: PreviewEnv;\n actions?: ReactNode;\n}\n\n/**\n * PreviewEnvCard — preview environment card surfacing all services from one PR.\n *\n * Theo's killer feature: full-stack preview environments. The card shows:\n * - PR number + title at the top\n * - branch + author in the metadata row\n * - one badge per service with its own status + URL\n * - bottom action row (Open, Promote, Delete)\n */\nconst PreviewEnvCard = forwardRef<HTMLDivElement, PreviewEnvCardProps>(\n ({ className, env, actions, ...props }, ref) => (\n <article\n ref={ref}\n className={cn(\n \"rounded-xl border bg-card p-5 shadow-sm\",\n \"transition-[border-color,box-shadow] duration-base ease-out-soft\",\n \"hover:border-primary/40\",\n className,\n )}\n {...(props as HTMLAttributes<HTMLDivElement>)}\n >\n <header className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"flex items-center gap-2 font-mono text-label-caps text-muted-foreground uppercase\">\n <GitPullRequest className=\"size-3\" /> PR #{env.prNumber}\n </p>\n <h3 className=\"mt-1 truncate font-display text-title-md tracking-tight\">{env.prTitle}</h3>\n <p className=\"mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-body-sm text-muted-foreground\">\n <span className=\"font-mono text-code-sm\">{env.branch}</span>\n {env.author ? (\n <>\n <span aria-hidden=\"true\">·</span>\n <span>by {env.author.name}</span>\n </>\n ) : null}\n <span aria-hidden=\"true\">·</span>\n <span>opened {env.createdAt}</span>\n </p>\n </div>\n <Badge variant=\"primary\">\n <Server className=\"size-3\" /> {env.services.length} service\n {env.services.length === 1 ? \"\" : \"s\"}\n </Badge>\n </header>\n\n <ul className=\"mt-4 divide-y divide-border/30 rounded-lg border border-border/30\">\n {env.services.map((s) => {\n // T3.3 (SEC-003): defang dangerous URL protocols before rendering\n // as <a href>. Consumers passing user-controlled URLs from API\n // responses are protected from javascript:/vbscript:/data:text/html\n // XSS payloads.\n const sanitized = safeHref(s.url);\n return (\n <li key={s.name} className=\"flex items-center justify-between gap-3 px-3 py-2\">\n <span className=\"font-mono text-code-sm text-foreground\">{s.name}</span>\n <div className=\"flex items-center gap-2\">\n {sanitized ? (\n <a\n href={sanitized}\n className=\"inline-flex items-center gap-1 font-mono text-code-sm text-primary hover:underline\"\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n {sanitized.replace(/^https?:\\/\\//, \"\")}\n <ExternalLink className=\"size-3\" />\n </a>\n ) : (\n <span className=\"font-mono text-code-sm text-muted-foreground\">internal</span>\n )}\n <Badge variant={statusToVariant[s.status]}>\n <Badge.Dot\n tone={statusToDot[s.status]}\n pulse={\n s.status === \"building\" || s.status === \"deploying\" || s.status === \"queued\"\n }\n />\n {statusLabels[s.status]}\n </Badge>\n </div>\n </li>\n );\n })}\n </ul>\n\n {actions ? <div className=\"mt-4 flex items-center gap-2\">{actions}</div> : null}\n </article>\n ),\n);\nPreviewEnvCard.displayName = \"PreviewEnvCard\";\n\nexport { PreviewEnvCard };\n"
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "preview-panel",
4
+ "type": "registry:ui",
5
+ "title": "PreviewPanel",
6
+ "description": "Browser preview with controls + integrated logs slot.",
7
+ "dependencies": [],
8
+ "registryDependencies": [
9
+ "https://usetheodev.github.io/theo-ui/r/browser-controls.json",
10
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
11
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "components/composites/preview-panel/preview-panel.tsx",
16
+ "type": "registry:ui",
17
+ "target": "components/ui/preview-panel.tsx",
18
+ "content": "import { forwardRef } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport { BrowserControls } from \"@/components/ui/browser-controls\";\n\ninterface PreviewPanelProps extends Omit<HTMLAttributes<HTMLElement>, \"content\"> {\n url: string;\n onUrlChange?: (next: string) => void;\n onBack?: () => void;\n onForward?: () => void;\n onReload?: () => void;\n /**\n * Region rendered as the preview body. Typically an <iframe>.\n */\n content: ReactNode;\n /**\n * Optional logs section rendered below the preview (e.g. dev server output).\n */\n logsSlot?: ReactNode;\n}\n\n/**\n * PreviewPanel — browser preview with controls + integrated logs slot.\n *\n * The Code workspace shows live dev-server URL + HMR logs side-by-side; this\n * panel keeps both in a single card so the user doesn't switch contexts.\n */\nconst PreviewPanel = forwardRef<HTMLElement, PreviewPanelProps>(\n (\n { className, url, onUrlChange, onBack, onForward, onReload, content, logsSlot, ...props },\n ref,\n ) => (\n <section\n ref={ref}\n className={cn(\"flex h-full flex-col overflow-hidden rounded-xl border bg-card\", className)}\n {...props}\n >\n <BrowserControls\n url={url}\n {...(onUrlChange ? { onUrlChange } : {})}\n {...(onBack ? { onBack } : {})}\n {...(onForward ? { onForward } : {})}\n {...(onReload ? { onReload } : {})}\n />\n <div className=\"flex-1 overflow-hidden bg-background\">{content}</div>\n {logsSlot ? (\n <div className=\"max-h-48 overflow-auto border-border/40 border-t bg-card\">{logsSlot}</div>\n ) : null}\n </section>\n ),\n);\nPreviewPanel.displayName = \"PreviewPanel\";\n\nexport { PreviewPanel };\n"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "progress-checklist",
4
+ "type": "registry:ui",
5
+ "title": "ProgressChecklist",
6
+ "description": "Right-inspector checklist tracking subtask completion with success / running / pending tones.",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
12
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json",
13
+ "https://usetheodev.github.io/theo-ui/r/task-types.json"
14
+ ],
15
+ "files": [
16
+ {
17
+ "path": "components/primitives/progress-checklist/progress-checklist.tsx",
18
+ "type": "registry:ui",
19
+ "target": "components/ui/progress-checklist.tsx",
20
+ "content": "import { Check, CircleDashed, Loader2 } from \"lucide-react\";\nimport { forwardRef } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport type { TaskStep, TaskStepStatus } from \"@/types/task\";\n\nconst statusIcon = {\n pending: CircleDashed,\n running: Loader2,\n done: Check,\n skipped: CircleDashed,\n} as const;\n\nconst statusToneText: Record<TaskStepStatus, string> = {\n pending: \"text-muted-foreground\",\n running: \"text-primary\",\n done: \"text-success line-through\",\n skipped: \"text-muted-foreground line-through\",\n};\n\nconst statusBg: Record<TaskStepStatus, string> = {\n pending: \"bg-muted text-muted-foreground\",\n running: \"bg-primary text-primary-foreground\",\n done: \"bg-success text-success-foreground\",\n skipped: \"bg-muted text-muted-foreground\",\n};\n\ninterface ProgressChecklistProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n title?: ReactNode;\n steps: TaskStep[];\n /**\n * If true, shows percentage bar for running steps with `progress`.\n */\n showProgressBars?: boolean;\n}\n\n/**\n * ProgressChecklist — right-inspector checklist.\n *\n * Visual: vertical list of steps with status dot, label, optional progress bar.\n * Matches WIREMOCKS §3 / §4 (\"Progresso\") with checkmarks and pulse on running.\n */\nconst ProgressChecklist = forwardRef<HTMLDivElement, ProgressChecklistProps>(\n ({ className, title, steps, showProgressBars = true, ...props }, ref) => (\n <section ref={ref} className={cn(\"rounded-xl border bg-card p-4\", className)} {...props}>\n {title ? (\n <header className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"font-display text-title-md tracking-tight\">{title}</h3>\n </header>\n ) : null}\n <ol className=\"grid gap-3\">\n {steps.map((step) => {\n const Icon = statusIcon[step.status];\n return (\n <li key={step.id} className=\"grid grid-cols-[auto_1fr] items-start gap-3\">\n <span\n className={cn(\n \"mt-0.5 grid size-5 place-items-center rounded-full\",\n statusBg[step.status],\n )}\n aria-hidden=\"true\"\n >\n <Icon className={cn(\"size-3\", step.status === \"running\" && \"animate-spin\")} />\n </span>\n <div className=\"min-w-0\">\n <p className={cn(\"text-body-sm\", statusToneText[step.status])}>{step.label}</p>\n {showProgressBars && step.status === \"running\" && step.progress !== undefined ? (\n <div className=\"mt-1.5 h-1 w-full overflow-hidden rounded-full bg-muted\">\n <div\n className=\"h-full bg-primary transition-[width] duration-base ease-out-soft\"\n style={{ width: `${Math.round(step.progress * 100)}%` }}\n />\n </div>\n ) : null}\n </div>\n </li>\n );\n })}\n </ol>\n </section>\n ),\n);\nProgressChecklist.displayName = \"ProgressChecklist\";\n\nexport { ProgressChecklist };\n"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "progress",
4
+ "type": "registry:ui",
5
+ "title": "Progress",
6
+ "description": "Accessible progress bar primitive with intent variants (default, success, warning, destructive), 4 heights (h-1 / h-1.5 / h-2 / h-3), and an indeterminate animated state. Built on role=\"progressbar\" + ARIA semantics.",
7
+ "dependencies": [],
8
+ "registryDependencies": [
9
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
10
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
11
+ ],
12
+ "files": [
13
+ {
14
+ "path": "components/primitives/progress/progress.tsx",
15
+ "type": "registry:ui",
16
+ "target": "components/ui/progress.tsx",
17
+ "content": "import { forwardRef } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Progress — accessible progress bar primitive.\n *\n * Built on `<div role=\"progressbar\">` (NOT native `<progress>`) so Tailwind\n * classes can style the track + fill cross-browser (Chrome/Safari/Firefox\n * shadow-DOM hooks for `<progress>` diverge). Matches Radix / shadcn /\n * Mantine convention.\n *\n * Variants:\n * - intent: `default` | `success` | `warning` | `destructive` — controls fill color\n * - height: `h-1` (4px, default) | `h-1.5` | `h-2` | `h-3`\n * - indeterminate: animated bar, no value (e.g. \"uploading…\", \"building…\")\n *\n * Composition:\n * <Progress value={42} max={100} intent=\"success\" aria-label=\"Upload\" />\n * <Progress indeterminate aria-label=\"Building\" />\n *\n * A11y:\n * - role=\"progressbar\"\n * - aria-valuenow / aria-valuemin / aria-valuemax (determinate)\n * - aria-busy=\"true\" when indeterminate\n * - Respects `prefers-reduced-motion` (no animation when set)\n *\n * Used by `<UsageMeter>` to render each metric's fill bar, but ships as a\n * standalone primitive for direct consumer use (deploy phase, file upload,\n * build progress, quota fill).\n */\n\nexport interface ProgressProps extends Omit<HTMLAttributes<HTMLDivElement>, \"role\"> {\n /** Current value (0..max). Values outside the range are clamped. */\n value?: number;\n /** Maximum value. Defaults to 100. */\n max?: number;\n /** Visual intent — controls fill color. */\n intent?: \"default\" | \"success\" | \"warning\" | \"destructive\";\n /** Bar height in tailwind units. Defaults to `\"h-1\"` (4px). */\n height?: \"h-1\" | \"h-1.5\" | \"h-2\" | \"h-3\";\n /** When true, animated bar with no value. Omits `aria-valuenow`, adds `aria-busy`. */\n indeterminate?: boolean;\n /** Accessible label. Required if not preceded by an `aria-labelledby` element. */\n \"aria-label\"?: string;\n}\n\nconst INTENT_FILL: Record<NonNullable<ProgressProps[\"intent\"]>, string> = {\n default: \"bg-primary\",\n success: \"bg-success\",\n warning: \"bg-warning\",\n destructive: \"bg-destructive\",\n};\n\nconst Progress = forwardRef<HTMLDivElement, ProgressProps>(\n (\n {\n className,\n value = 0,\n max = 100,\n intent = \"default\",\n height = \"h-1\",\n indeterminate = false,\n ...props\n },\n ref,\n ) => {\n const clampedMax = Math.max(0, max);\n const clampedValue = Math.min(clampedMax, Math.max(0, value));\n const percent = clampedMax > 0 ? (clampedValue / clampedMax) * 100 : 0;\n const fillClass = INTENT_FILL[intent];\n\n return (\n // biome-ignore lint/a11y/useFocusableInteractive: WAI-ARIA `progressbar` is a status role (https://www.w3.org/TR/wai-aria-1.2/#progressbar) — NOT supposed to be focusable; screen readers announce updates without keyboard navigation.\n <div\n ref={ref}\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={clampedMax}\n aria-valuenow={indeterminate ? undefined : clampedValue}\n aria-busy={indeterminate ? true : undefined}\n className={cn(\"relative w-full overflow-hidden rounded-full bg-muted\", height, className)}\n {...props}\n >\n {indeterminate ? (\n <div\n className={cn(\n \"absolute inset-y-0 left-0 w-1/3 rounded-full\",\n \"animate-[progress-indeterminate_1.4s_ease-in-out_infinite] motion-reduce:animate-none\",\n \"motion-reduce:w-full motion-reduce:opacity-50\",\n fillClass,\n )}\n />\n ) : (\n <div\n className={cn(\n \"h-full rounded-full transition-[width] duration-base ease-out-soft\",\n \"motion-reduce:transition-none\",\n fillClass,\n )}\n style={{ width: `${percent}%` }}\n />\n )}\n </div>\n );\n },\n);\nProgress.displayName = \"Progress\";\n\nexport { Progress };\n"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "project-card",
4
+ "type": "registry:block",
5
+ "title": "ProjectCard",
6
+ "description": "Surface for a project in a project listing.",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://usetheodev.github.io/theo-ui/r/badge.json",
12
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
13
+ "https://usetheodev.github.io/theo-ui/r/deployment-row.json",
14
+ "https://usetheodev.github.io/theo-ui/r/safe-href.json",
15
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
16
+ ],
17
+ "files": [
18
+ {
19
+ "path": "components/composites/project-card/project-card.tsx",
20
+ "type": "registry:block",
21
+ "target": "components/blocks/project-card.tsx",
22
+ "content": "import { Activity, GitBranch, GitCommit } from \"lucide-react\";\nimport { forwardRef } from \"react\";\nimport type { HTMLAttributes, ReactNode, Ref } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport { safeHref } from \"@/lib/safe-href\";\nimport { Badge } from \"@/components/ui/badge\";\nimport type { DeploymentStatus } from \"@/components/blocks/deployment-row\";\n\nconst statusToVariant: Record<\n DeploymentStatus,\n \"default\" | \"primary\" | \"success\" | \"warning\" | \"destructive\"\n> = {\n queued: \"warning\",\n building: \"primary\",\n deploying: \"primary\",\n live: \"success\",\n failed: \"destructive\",\n cancelled: \"default\",\n};\nconst statusToDotTone: Record<\n DeploymentStatus,\n \"primary\" | \"success\" | \"warning\" | \"destructive\" | \"muted\"\n> = {\n queued: \"warning\",\n building: \"primary\",\n deploying: \"primary\",\n live: \"success\",\n failed: \"destructive\",\n cancelled: \"muted\",\n};\n\nexport interface Project {\n id: string;\n name: string;\n description?: string;\n framework?: string;\n branch: string;\n commitSha: string;\n commitMessage?: string;\n status: DeploymentStatus;\n url?: string;\n region?: string;\n lastDeployedAt: string;\n}\n\ninterface ProjectCardProps extends HTMLAttributes<HTMLAnchorElement | HTMLDivElement> {\n project: Project;\n href?: string;\n actions?: ReactNode;\n /**\n * Show the project description and commit message. Default true.\n */\n detailed?: boolean;\n}\n\n/**\n * ProjectCard — surface for a project in a project listing.\n *\n * Light hover lift (no shadow inflation), violet ring on focus,\n * status badge with optional pulse, framework + region in muted footer.\n */\nconst ProjectCard = forwardRef<HTMLElement, ProjectCardProps>(\n ({ className, project, href, actions, detailed = true, ...props }, ref) => {\n // T3.3 (SEC-003): defang javascript:/vbscript:/data:text/html before\n // rendering as <a href>. Consumers passing user-controlled URLs are\n // protected from XSS via dangerous protocols.\n const sanitizedHref = safeHref(href);\n const isLink = sanitizedHref !== undefined;\n const Tag = isLink ? \"a\" : \"div\";\n const isAnimated =\n project.status === \"building\" ||\n project.status === \"deploying\" ||\n project.status === \"queued\";\n\n const content = (\n <>\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate font-display text-title-md tracking-tight\">{project.name}</h3>\n {project.framework ? (\n <p className=\"mt-0.5 font-mono text-label-caps text-muted-foreground uppercase\">\n {project.framework}\n </p>\n ) : null}\n </div>\n <Badge variant={statusToVariant[project.status]}>\n <Badge.Dot tone={statusToDotTone[project.status]} pulse={isAnimated} />\n {project.status === \"live\"\n ? \"Live\"\n : project.status === \"building\"\n ? \"Building\"\n : project.status === \"deploying\"\n ? \"Deploying\"\n : project.status === \"queued\"\n ? \"Queued\"\n : project.status === \"failed\"\n ? \"Failed\"\n : \"Cancelled\"}\n </Badge>\n </div>\n\n {detailed && project.description ? (\n <p className=\"line-clamp-2 text-body-sm text-muted-foreground\">{project.description}</p>\n ) : null}\n\n {detailed && project.commitMessage ? (\n <p className=\"line-clamp-1 font-mono text-code-sm text-foreground/80\">\n {project.commitMessage}\n </p>\n ) : null}\n\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 font-mono text-code-sm text-muted-foreground\">\n <span className=\"inline-flex items-center gap-1\">\n <GitBranch className=\"size-3\" /> {project.branch}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <GitCommit className=\"size-3\" /> {project.commitSha.slice(0, 7)}\n </span>\n {project.region ? (\n <span className=\"inline-flex items-center gap-1\">\n <Activity className=\"size-3\" /> {project.region}\n </span>\n ) : null}\n <span aria-hidden=\"true\">·</span>\n <span>{project.lastDeployedAt}</span>\n </div>\n\n {actions ? <div className=\"flex items-center gap-2 pt-2\">{actions}</div> : null}\n </>\n );\n\n return (\n <Tag\n ref={ref as Ref<HTMLAnchorElement & HTMLDivElement>}\n href={sanitizedHref}\n className={cn(\n \"group relative flex flex-col gap-3 rounded-xl border bg-card p-5 shadow-sm\",\n \"transition-[box-shadow,transform,border-color] duration-base ease-out-soft\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n isLink && \"hover:-translate-y-px cursor-pointer hover:border-primary/50 hover:shadow-md\",\n className,\n )}\n {...(props as HTMLAttributes<HTMLAnchorElement> & HTMLAttributes<HTMLDivElement>)}\n >\n {content}\n </Tag>\n );\n },\n);\nProjectCard.displayName = \"ProjectCard\";\n\nexport { ProjectCard };\n"
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "project-switcher",
4
+ "type": "registry:ui",
5
+ "title": "ProjectSwitcher",
6
+ "description": "Sidebar header for a code agent app.",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
12
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "components/primitives/project-switcher/project-switcher.tsx",
17
+ "type": "registry:ui",
18
+ "target": "components/ui/project-switcher.tsx",
19
+ "content": "import { ChevronsUpDown, GitBranch } from \"lucide-react\";\nimport { forwardRef } from \"react\";\nimport type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * ProjectSwitcher — sidebar header for a code agent app.\n *\n * Shows the active workspace (folder name) + branch + status dot + a tiny\n * `ChevronsUpDown` hint when clickable. Used in Sidebar.Header to anchor the\n * current project context for the session list below.\n *\n * <Sidebar.Header className=\"p-0\">\n * <ProjectSwitcher\n * workspace=\"acme-web\"\n * branch=\"claude/alignment-grid\"\n * status=\"running\"\n * onClick={openProjectPicker}\n * />\n * </Sidebar.Header>\n *\n * If `onClick` is omitted, renders as a static `<div>` (no chevron, not focusable).\n */\n\nexport type ProjectStatus = \"idle\" | \"running\" | \"error\" | \"offline\";\n\nconst STATUS_CLASS: Record<ProjectStatus, string> = {\n idle: \"bg-muted-foreground/40\",\n running: \"bg-success animate-pulse\",\n error: \"bg-destructive\",\n offline: \"bg-muted-foreground/20\",\n};\n\nconst STATUS_LABEL: Record<ProjectStatus, string> = {\n idle: \"Idle\",\n running: \"Agent running\",\n error: \"Error\",\n offline: \"Offline\",\n};\n\ninterface ProjectSwitcherProps\n extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"type\" | \"children\"> {\n /** Workspace / folder name (e.g. \"acme-web\"). */\n workspace: ReactNode;\n /** Optional git branch. Renders inline with a GitBranch icon. */\n branch?: ReactNode;\n /** Optional status dot. Defaults to \"idle\". */\n status?: ProjectStatus;\n /** Brand letter / icon shown in the violet tile. Default: first char of workspace if string. */\n brand?: ReactNode;\n}\n\nconst ProjectSwitcher = forwardRef<HTMLButtonElement, ProjectSwitcherProps>(\n ({ className, workspace, branch, status = \"idle\", brand, onClick, disabled, ...props }, ref) => {\n const isInteractive = !!onClick;\n const inferredBrand =\n brand ?? (typeof workspace === \"string\" ? workspace.charAt(0).toUpperCase() : \"·\");\n const content = (\n <>\n <span\n className=\"grid size-8 shrink-0 place-items-center rounded-lg bg-primary font-black font-display text-primary-foreground\"\n aria-hidden=\"true\"\n >\n {inferredBrand}\n </span>\n <div className=\"grid min-w-0 flex-1 text-left\">\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <span className=\"truncate font-display text-title-md leading-none\">{workspace}</span>\n <span\n className={cn(\"size-1.5 shrink-0 rounded-full\", STATUS_CLASS[status])}\n aria-label={STATUS_LABEL[status]}\n role=\"img\"\n />\n </div>\n {branch ? (\n <span className=\"mt-1 inline-flex items-center gap-1 truncate font-mono text-label text-muted-foreground\">\n <GitBranch className=\"size-3 shrink-0\" aria-hidden=\"true\" /> {branch}\n </span>\n ) : null}\n </div>\n {isInteractive ? (\n <ChevronsUpDown className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n ) : null}\n </>\n );\n\n if (!isInteractive) {\n return (\n <div\n className={cn(\"flex w-full items-center gap-3 px-5 py-3 text-card-foreground\", className)}\n aria-disabled={disabled || undefined}\n >\n {content}\n </div>\n );\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n className={cn(\n \"flex w-full items-center gap-3 px-5 py-3 text-card-foreground\",\n \"transition-colors hover:bg-muted/40\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n {content}\n </button>\n );\n },\n);\nProjectSwitcher.displayName = \"ProjectSwitcher\";\n\nexport { ProjectSwitcher };\n"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "quick-action-chips",
4
+ "type": "registry:ui",
5
+ "title": "QuickActionChips",
6
+ "description": "Row of intent chips below a hero composer.",
7
+ "dependencies": [],
8
+ "registryDependencies": [
9
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
10
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json",
11
+ "https://usetheodev.github.io/theo-ui/r/types.json"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "components/primitives/quick-action-chips/quick-action-chips.tsx",
16
+ "type": "registry:ui",
17
+ "target": "components/ui/quick-action-chips.tsx",
18
+ "content": "import { forwardRef } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport type { IconComponent } from \"@/lib/types\";\n\nexport interface QuickAction {\n id: string;\n label: ReactNode;\n /** Icon component (e.g. from lucide-react). */\n icon?: IconComponent;\n /** When true, the chip is highlighted as the suggested next action. */\n primary?: boolean;\n}\n\ninterface QuickActionChipsProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n actions: QuickAction[];\n onSelect?: (id: string) => void;\n}\n\n/**\n * QuickActionChips — row of intent chips below a hero composer.\n *\n * Used in Chat Home (\"Escrever / Aprender / Código / Assuntos pessoais\")\n * and the Files panel.\n */\nconst QuickActionChips = forwardRef<HTMLDivElement, QuickActionChipsProps>(\n ({ className, actions, onSelect, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex flex-wrap items-center justify-center gap-2\", className)}\n {...props}\n >\n {actions.map((a) => {\n const Icon = a.icon;\n return (\n <button\n key={a.id}\n type=\"button\"\n onClick={() => onSelect?.(a.id)}\n className={cn(\n \"inline-flex h-9 items-center gap-2 rounded-full border px-4\",\n \"font-medium font-sans text-body-sm\",\n \"transition-[box-shadow,background-color,border-color,color] duration-base ease-out-soft\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n a.primary\n ? \"border-transparent bg-primary text-primary-foreground hover:shadow-glow\"\n : \"border-border/60 bg-card text-foreground hover:border-primary/40 hover:bg-muted\",\n )}\n >\n {Icon ? <Icon className=\"size-4\" /> : null}\n {a.label}\n </button>\n );\n })}\n </div>\n ),\n);\nQuickActionChips.displayName = \"QuickActionChips\";\n\nexport { QuickActionChips };\n"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "radio-group",
4
+ "type": "registry:ui",
5
+ "title": "RadioGroup",
6
+ "description": "Built on Radix RadioGroup — accessible radio group with roving focus and orientation control.",
7
+ "dependencies": [
8
+ "@radix-ui/react-radio-group",
9
+ "lucide-react"
10
+ ],
11
+ "registryDependencies": [
12
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
13
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
14
+ ],
15
+ "files": [
16
+ {
17
+ "path": "components/primitives/radio-group/radio-group.tsx",
18
+ "type": "registry:ui",
19
+ "target": "components/ui/radio-group.tsx",
20
+ "content": "import * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport { Circle } from \"lucide-react\";\nimport { forwardRef } from \"react\";\nimport type { ComponentPropsWithoutRef, ElementRef } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * RadioGroup — built on Radix RadioGroup.\n *\n * Composition:\n * <RadioGroup value={v} onValueChange={setV}>\n * <RadioGroup.Item value=\"a\" id=\"a\" />\n * <Label htmlFor=\"a\">Option A</Label>\n * </RadioGroup>\n *\n * Group spaces items by 0.75rem vertically; switch to grid utilities if you\n * need horizontal/grid layouts.\n */\n\nconst RadioGroupRoot = forwardRef<\n ElementRef<typeof RadioGroupPrimitive.Root>,\n ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <RadioGroupPrimitive.Root ref={ref} className={cn(\"grid gap-3\", className)} {...props} />\n));\nRadioGroupRoot.displayName = \"RadioGroup\";\n\nconst RadioGroupItem = forwardRef<\n ElementRef<typeof RadioGroupPrimitive.Item>,\n ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, ...props }, ref) => (\n <RadioGroupPrimitive.Item\n ref={ref}\n className={cn(\n \"aspect-square size-4 rounded-full border border-border bg-card text-primary\",\n \"transition-[border-color,box-shadow] duration-base ease-out-soft\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"data-[state=checked]:border-primary\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n <RadioGroupPrimitive.Indicator className=\"flex items-center justify-center\">\n <Circle className=\"size-2 fill-primary text-primary\" aria-hidden=\"true\" />\n </RadioGroupPrimitive.Indicator>\n </RadioGroupPrimitive.Item>\n));\nRadioGroupItem.displayName = \"RadioGroup.Item\";\n\nconst RadioGroup = /*#__PURE__*/ Object.assign(RadioGroupRoot, {\n Item: RadioGroupItem,\n});\n\nexport { RadioGroup };\n"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "recent-folders-list",
4
+ "type": "registry:ui",
5
+ "title": "RecentFoldersList",
6
+ "description": "Recently-used folders for the Files picker.",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
12
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "components/primitives/recent-folders-list/recent-folders-list.tsx",
17
+ "type": "registry:ui",
18
+ "target": "components/ui/recent-folders-list.tsx",
19
+ "content": "import { Folder } from \"lucide-react\";\nimport { forwardRef } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface RecentFolder {\n id: string;\n name: ReactNode;\n path: string;\n /** When true, the row is highlighted as selected. */\n active?: boolean;\n}\n\ninterface RecentFoldersListProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"title\" | \"onSelect\"> {\n title?: ReactNode;\n folders: RecentFolder[];\n onSelect?: (id: string) => void;\n}\n\n/**\n * RecentFoldersList — recently-used folders for the Files picker.\n *\n * Visual: a stack of rows with folder icon + name + path (smaller, muted),\n * active row highlighted with violet bg.\n */\nconst RecentFoldersList = forwardRef<HTMLDivElement, RecentFoldersListProps>(\n ({ className, title = \"Recent folders\", folders, onSelect, ...props }, ref) => (\n <div ref={ref} className={cn(\"rounded-xl border bg-card\", className)} {...props}>\n {title ? (\n <p className=\"border-border/40 border-b px-3 py-2 font-sans text-label-caps text-muted-foreground uppercase tracking-wider\">\n {title}\n </p>\n ) : null}\n <ul>\n {folders.map((folder) => (\n <li key={folder.id}>\n <button\n type=\"button\"\n onClick={() => onSelect?.(folder.id)}\n className={cn(\n \"flex w-full items-center gap-3 px-3 py-2\",\n \"text-left transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n folder.active ? \"bg-primary/10 text-primary\" : \"hover:bg-muted\",\n )}\n >\n <Folder\n className={cn(\n \"size-4 shrink-0\",\n folder.active ? \"text-primary\" : \"text-muted-foreground\",\n )}\n aria-hidden=\"true\"\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-medium text-body-sm\">{folder.name}</p>\n <p className=\"truncate font-mono text-label text-muted-foreground\">{folder.path}</p>\n </div>\n </button>\n </li>\n ))}\n </ul>\n </div>\n ),\n);\nRecentFoldersList.displayName = \"RecentFoldersList\";\n\nexport { RecentFoldersList };\n"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "rollback-ui",
4
+ "type": "registry:block",
5
+ "title": "RollbackUI",
6
+ "description": "Instant rollback selector showing recent versions.",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://usetheodev.github.io/theo-ui/r/badge.json",
12
+ "https://usetheodev.github.io/theo-ui/r/button.json",
13
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
14
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "components/composites/rollback-ui/rollback-ui.tsx",
19
+ "type": "registry:block",
20
+ "target": "components/blocks/rollback-ui.tsx",
21
+ "content": "import { ArrowDownLeft, Clock, GitCommit, RotateCcw } from \"lucide-react\";\nimport { forwardRef, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\n\nexport interface RollbackTarget {\n id: string;\n version: string;\n commitSha: string;\n commitMessage: string;\n deployedAt: string;\n isCurrent?: boolean;\n /**\n * Optional duration of the deploy (e.g. \"24s\") for context.\n */\n duration?: string;\n}\n\ninterface RollbackUIProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Deployment history, newest first. The current deploy should have `isCurrent: true`.\n */\n history: RollbackTarget[];\n /**\n * Fires when user confirms rollback to a specific target.\n */\n onRollback?: (targetId: string) => void | Promise<void>;\n}\n\n/**\n * RollbackUI — instant rollback selector showing recent versions.\n *\n * The current deploy is marked, every other version offers a \"Roll back\" button.\n * On select, the row enters confirm state (Confirm / Cancel buttons inline) before\n * firing onRollback. This protects against accidental rollbacks while still being one click.\n */\nconst RollbackUI = forwardRef<HTMLDivElement, RollbackUIProps>(\n ({ className, history, onRollback, ...props }, ref) => {\n const [confirmId, setConfirmId] = useState<string | null>(null);\n const [pendingId, setPendingId] = useState<string | null>(null);\n\n const trigger = async (id: string) => {\n setPendingId(id);\n try {\n await onRollback?.(id);\n } finally {\n setPendingId(null);\n setConfirmId(null);\n }\n };\n\n return (\n <div\n ref={ref}\n className={cn(\"rounded-xl border bg-card p-5 shadow-sm\", className)}\n {...props}\n >\n <header className=\"mb-4 flex items-baseline justify-between gap-3\">\n <div>\n <h3 className=\"font-display text-title-md tracking-tight\">Rollback</h3>\n <p className=\"text-body-sm text-muted-foreground\">\n Instant rollback to a previous version. Verified in under 5 seconds.\n </p>\n </div>\n </header>\n\n <ol className=\"grid gap-2\">\n {history.map((target, idx) => {\n const isCurrent = target.isCurrent ?? idx === 0;\n const isConfirming = confirmId === target.id;\n const isPending = pendingId === target.id;\n return (\n <li\n key={target.id}\n className={cn(\n \"grid grid-cols-[auto_1fr_auto] items-center gap-3 rounded-lg border p-3\",\n isCurrent ? \"border-primary/40 bg-primary/5\" : \"border-border/40 bg-card\",\n )}\n >\n <span\n className={cn(\n \"grid size-8 place-items-center rounded-md\",\n isCurrent\n ? \"bg-primary text-primary-foreground\"\n : \"bg-muted text-muted-foreground\",\n )}\n aria-hidden=\"true\"\n >\n {isCurrent ? <Clock className=\"size-4\" /> : <GitCommit className=\"size-4\" />}\n </span>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-mono text-code-sm text-foreground\">{target.version}</span>\n {isCurrent ? <Badge variant=\"success\">Current</Badge> : null}\n {target.duration ? (\n <span className=\"font-mono text-code-sm text-muted-foreground\">\n · {target.duration}\n </span>\n ) : null}\n </div>\n <p className=\"mt-0.5 truncate text-body-sm text-muted-foreground\">\n <span className=\"font-mono\">{target.commitSha.slice(0, 7)}</span> ·{\" \"}\n {target.commitMessage} · {target.deployedAt}\n </p>\n </div>\n <div>\n {isCurrent ? null : isConfirming ? (\n <div className=\"flex items-center gap-1\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => setConfirmId(null)}\n disabled={isPending}\n >\n Cancel\n </Button>\n <Button size=\"sm\" onClick={() => trigger(target.id)} disabled={isPending}>\n <RotateCcw /> Confirm rollback\n </Button>\n </div>\n ) : (\n <Button size=\"sm\" variant=\"secondary\" onClick={() => setConfirmId(target.id)}>\n <ArrowDownLeft /> Roll back\n </Button>\n )}\n </div>\n </li>\n );\n })}\n </ol>\n </div>\n );\n },\n);\nRollbackUI.displayName = \"RollbackUI\";\n\nexport { RollbackUI };\n"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "rule-card",
4
+ "type": "registry:ui",
5
+ "title": "RuleCard",
6
+ "description": "Single Rule row in the Rules list.",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
12
+ "https://usetheodev.github.io/theo-ui/r/rule-types.json",
13
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json"
14
+ ],
15
+ "files": [
16
+ {
17
+ "path": "components/primitives/rule-card/rule-card.tsx",
18
+ "type": "registry:ui",
19
+ "target": "components/ui/rule-card.tsx",
20
+ "content": "import { Globe, Pencil, Trash2 } from \"lucide-react\";\nimport { forwardRef } from \"react\";\nimport type { HTMLAttributes, MouseEvent } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport type { Rule, RuleScope, RuleState } from \"@/types/rule\";\n\n/**\n * RuleCard — single Rule row in the Rules list. Renders title, scope/state\n * badges, optional tag chips, a truncated body preview, and edit/toggle/delete\n * actions in the corner.\n */\n\nconst SCOPE_LABEL: Record<RuleScope, string> = {\n global: \"Global\",\n project: \"Project\",\n};\n\nconst SCOPE_CLASS: Record<RuleScope, string> = {\n global: \"bg-primary/15 text-primary\",\n project: \"bg-accent/15 text-accent\",\n};\n\ninterface RuleCardProps\n extends Omit<HTMLAttributes<HTMLElement>, \"title\" | \"onSelect\" | \"onToggle\"> {\n rule: Rule;\n /** Click on the card body — typically opens detail/edit view. */\n onSelect?: (id: string) => void;\n /** Click on the edit pencil. Defaults to onSelect if omitted. */\n onEdit?: (id: string) => void;\n /** Click on the trash icon. Renders the icon only when provided. */\n onDelete?: (id: string) => void;\n /** Toggle enabled/disabled. Renders the switch-like dot only when provided. */\n onToggle?: (id: string, next: RuleState) => void;\n}\n\nconst RuleCard = forwardRef<HTMLElement, RuleCardProps>(\n ({ className, rule, onSelect, onEdit, onDelete, onToggle, ...props }, ref) => {\n const handleEdit = (e: MouseEvent) => {\n e.stopPropagation();\n (onEdit ?? onSelect)?.(rule.id);\n };\n const handleDelete = (e: MouseEvent) => {\n e.stopPropagation();\n onDelete?.(rule.id);\n };\n const handleToggle = (e: MouseEvent) => {\n e.stopPropagation();\n onToggle?.(rule.id, rule.state === \"enabled\" ? \"disabled\" : \"enabled\");\n };\n return (\n <article\n ref={ref}\n className={cn(\n \"grid gap-2 rounded-lg border border-border/40 bg-card/40 p-3\",\n \"transition-colors duration-base ease-out-soft\",\n onSelect && \"cursor-pointer hover:border-border hover:bg-card/70\",\n rule.state === \"disabled\" && \"opacity-60\",\n className,\n )}\n onClick={onSelect ? () => onSelect(rule.id) : undefined}\n {...props}\n >\n <header className=\"flex items-start gap-2\">\n <h4 className=\"flex-1 truncate font-display text-foreground text-title-md tracking-tight\">\n {rule.title}\n </h4>\n <span\n className={cn(\n \"inline-flex h-5 shrink-0 items-center gap-1 rounded-md px-2 font-medium font-mono text-label tracking-tight\",\n SCOPE_CLASS[rule.scope],\n )}\n >\n <Globe className=\"size-3\" aria-hidden=\"true\" /> {SCOPE_LABEL[rule.scope]}\n </span>\n </header>\n <p className=\"line-clamp-2 text-body-sm text-muted-foreground\">{rule.body}</p>\n <footer className=\"flex items-center justify-between gap-2\">\n <div className=\"flex flex-wrap items-center gap-1\">\n {rule.tags?.map((t) => (\n <span\n key={t}\n className=\"inline-flex h-4 items-center rounded bg-muted px-1.5 font-mono text-label-caps text-muted-foreground uppercase tracking-wider\"\n >\n {t}\n </span>\n ))}\n {rule.updatedAt ? (\n <span className=\"font-mono text-label text-muted-foreground tabular-nums\">\n · {rule.updatedAt}\n </span>\n ) : null}\n </div>\n <div className=\"flex items-center gap-1\">\n {onToggle ? (\n <button\n type=\"button\"\n onClick={handleToggle}\n aria-label={rule.state === \"enabled\" ? \"Disable rule\" : \"Enable rule\"}\n className={cn(\n \"inline-flex h-6 items-center rounded-full px-2 font-mono text-label\",\n rule.state === \"enabled\"\n ? \"bg-success/15 text-success\"\n : \"bg-muted text-muted-foreground\",\n )}\n >\n {rule.state === \"enabled\" ? \"Enabled\" : \"Disabled\"}\n </button>\n ) : null}\n <button\n type=\"button\"\n onClick={handleEdit}\n aria-label=\"Edit rule\"\n className=\"grid size-6 place-items-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <Pencil className=\"size-3.5\" />\n </button>\n {onDelete ? (\n <button\n type=\"button\"\n onClick={handleDelete}\n aria-label=\"Delete rule\"\n className=\"grid size-6 place-items-center rounded-md text-muted-foreground hover:bg-destructive/10 hover:text-destructive\"\n >\n <Trash2 className=\"size-3.5\" />\n </button>\n ) : null}\n </div>\n </footer>\n </article>\n );\n },\n);\nRuleCard.displayName = \"RuleCard\";\n\nexport { RuleCard };\n"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "rule-editor",
4
+ "type": "registry:block",
5
+ "title": "RuleEditor",
6
+ "description": "Form for creating or editing a Rule (behavior instruction",
7
+ "dependencies": [],
8
+ "registryDependencies": [
9
+ "https://usetheodev.github.io/theo-ui/r/button.json",
10
+ "https://usetheodev.github.io/theo-ui/r/cn.json",
11
+ "https://usetheodev.github.io/theo-ui/r/form-field.json",
12
+ "https://usetheodev.github.io/theo-ui/r/input.json",
13
+ "https://usetheodev.github.io/theo-ui/r/mode-types.json",
14
+ "https://usetheodev.github.io/theo-ui/r/rule-types.json",
15
+ "https://usetheodev.github.io/theo-ui/r/select.json",
16
+ "https://usetheodev.github.io/theo-ui/r/switch.json",
17
+ "https://usetheodev.github.io/theo-ui/r/tailwind-preset.json",
18
+ "https://usetheodev.github.io/theo-ui/r/textarea.json"
19
+ ],
20
+ "files": [
21
+ {
22
+ "path": "components/composites/rule-editor/rule-editor.tsx",
23
+ "type": "registry:block",
24
+ "target": "components/blocks/rule-editor.tsx",
25
+ "content": "import { useState } from \"react\";\nimport type { FormEvent, HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport { ALL_MODES, MODE_LABEL, type Mode } from \"@/types/mode\";\nimport type { Rule, RuleScope, RuleState } from \"@/types/rule\";\nimport { Button } from \"@/components/ui/button\";\nimport { FormField } from \"@/components/ui/form-field\";\nimport { Input } from \"@/components/ui/input\";\nimport { Select } from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Textarea } from \"@/components/ui/textarea\";\n\n/**\n * RuleEditor — form for creating or editing a Rule (behavior instruction\n * injected into the system prompt).\n */\n\ntype RuleDraft = Omit<Rule, \"id\" | \"updatedAt\"> & {\n id?: string;\n tags?: string[];\n};\n\ninterface RuleEditorProps extends Omit<HTMLAttributes<HTMLFormElement>, \"onSubmit\" | \"onChange\"> {\n initial?: Partial<Rule>;\n onSave: (draft: RuleDraft) => void;\n onCancel?: () => void;\n onDelete?: () => void;\n}\n\nconst SCOPES: Array<{ id: RuleScope; label: string }> = [\n { id: \"global\", label: \"Global — applies to every session\" },\n { id: \"project\", label: \"Project — only this workspace\" },\n];\n\nexport function RuleEditor({\n className,\n initial,\n onSave,\n onCancel,\n onDelete,\n ...formProps\n}: RuleEditorProps) {\n const [title, setTitle] = useState(initial?.title ?? \"\");\n const [body, setBody] = useState(initial?.body ?? \"\");\n const [scope, setScope] = useState<RuleScope>(initial?.scope ?? \"global\");\n const [tagsRaw, setTagsRaw] = useState(initial?.tags?.join(\", \") ?? \"\");\n const [enabled, setEnabled] = useState<RuleState>(initial?.state ?? \"enabled\");\n const [modes, setModes] = useState<Mode[]>(initial?.modes ?? []);\n\n // Note: state is only seeded once on mount. To reset the form when editing\n // a different rule, use the React `key` pattern at the call site:\n // <RuleEditor key={rule.id} initial={rule} ... />\n const toggleMode = (m: Mode) =>\n setModes((prev) => (prev.includes(m) ? prev.filter((x) => x !== m) : [...prev, m]));\n\n const canSave = title.trim().length > 0 && body.trim().length > 0;\n const handleSubmit = (e: FormEvent) => {\n e.preventDefault();\n if (!canSave) return;\n onSave({\n id: initial?.id,\n title: title.trim(),\n body: body.trim(),\n scope,\n state: enabled,\n tags: tagsRaw\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean),\n modes: modes.length > 0 ? modes : undefined,\n });\n };\n\n return (\n <form\n onSubmit={handleSubmit}\n className={cn(\"flex h-full flex-col gap-4\", className)}\n {...formProps}\n >\n <FormField>\n <FormField.Label>Title</FormField.Label>\n <FormField.Control>\n <Input\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"Always write tests before fixes\"\n required\n />\n </FormField.Control>\n <FormField.Hint>Short, imperative summary the agent will keep in memory.</FormField.Hint>\n </FormField>\n\n <FormField className=\"flex-1\">\n <FormField.Label>Body (markdown)</FormField.Label>\n <FormField.Control>\n <Textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n rows={8}\n placeholder=\"When fixing a bug, first write a failing regression test, then the fix.\"\n required\n className=\"min-h-[12rem] flex-1 font-mono text-code-sm\"\n />\n </FormField.Control>\n <FormField.Hint>Injected into the system prompt verbatim.</FormField.Hint>\n </FormField>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <FormField>\n <FormField.Label>Scope</FormField.Label>\n <FormField.Control>\n <Select\n value={scope}\n onValueChange={(v) => {\n // Re-audit Issue 7: narrow Select string value against\n // SCOPES.id before casting. Silent no-op for unknown.\n const next = SCOPES.find((s) => s.id === v);\n if (next) setScope(next.id);\n }}\n >\n <Select.Trigger aria-label=\"Select rule scope\">\n <Select.Value />\n </Select.Trigger>\n <Select.Content>\n {SCOPES.map((s) => (\n <Select.Item key={s.id} value={s.id}>\n {s.label}\n </Select.Item>\n ))}\n </Select.Content>\n </Select>\n </FormField.Control>\n </FormField>\n <FormField>\n <FormField.Label>Tags (comma-separated)</FormField.Label>\n <FormField.Control>\n <Input\n value={tagsRaw}\n onChange={(e) => setTagsRaw(e.target.value)}\n placeholder=\"testing, process\"\n />\n </FormField.Control>\n </FormField>\n </div>\n\n <FormField>\n <FormField.Label>Active modes</FormField.Label>\n <div className=\"flex flex-wrap gap-1.5\">\n {ALL_MODES.map((m) => {\n const on = modes.includes(m);\n return (\n <button\n key={m}\n type=\"button\"\n onClick={() => toggleMode(m)}\n aria-pressed={on}\n className={cn(\n \"inline-flex h-7 items-center rounded-full border px-3 font-mono text-body-sm transition-colors\",\n on\n ? \"border-primary bg-primary/15 text-primary\"\n : \"border-border/60 bg-card text-muted-foreground hover:text-foreground\",\n )}\n >\n {MODE_LABEL[m]}\n </button>\n );\n })}\n </div>\n <FormField.Hint>\n {modes.length === 0\n ? \"Empty = global (applies to every mode).\"\n : `Only visible in: ${modes.map((m) => MODE_LABEL[m]).join(\", \")}.`}\n </FormField.Hint>\n </FormField>\n\n <div className=\"flex items-center gap-3\">\n <Switch\n checked={enabled === \"enabled\"}\n onCheckedChange={(v) => setEnabled(v ? \"enabled\" : \"disabled\")}\n aria-label=\"Enabled\"\n />\n <span className=\"text-body-sm text-muted-foreground\">\n {enabled === \"enabled\"\n ? \"Enabled — agent will follow this rule.\"\n : \"Disabled — kept but ignored.\"}\n </span>\n </div>\n\n <footer className=\"flex items-center justify-between gap-2 border-border/40 border-t pt-4\">\n <div>\n {onDelete ? (\n <Button type=\"button\" variant=\"ghost\" onClick={onDelete}>\n Delete\n </Button>\n ) : null}\n </div>\n <div className=\"flex items-center gap-2\">\n {onCancel ? (\n <Button type=\"button\" variant=\"secondary\" onClick={onCancel}>\n Cancel\n </Button>\n ) : null}\n <Button type=\"submit\" disabled={!canSave}>\n {initial?.id ? \"Save changes\" : \"Create rule\"}\n </Button>\n </div>\n </footer>\n </form>\n );\n}\n"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "rule-types",
4
+ "type": "registry:lib",
5
+ "title": "Theo UI rule types",
6
+ "description": "Shared TypeScript types for Rules — user-authored behavior instructions injected into the system prompt.",
7
+ "files": [
8
+ {
9
+ "path": "types/rule.ts",
10
+ "type": "registry:lib",
11
+ "target": "types/rule.ts",
12
+ "content": "import type { Mode } from \"@/types/mode\";\n\n/**\n * Rule — a user-authored behavior instruction injected into the system prompt.\n *\n * Equivalent to a single markdown file under `.claude/rules/` in Claude Code:\n * short imperative text the user writes once and the agent follows always.\n * Rules can be scoped (`global` = applies to every session; `project` = only\n * inside the current workspace) and toggled on/off without deletion.\n */\n\nexport type RuleScope = \"global\" | \"project\";\nexport type RuleState = \"enabled\" | \"disabled\";\n\nexport interface Rule {\n id: string;\n /** Short title shown in the list. */\n title: string;\n /** Markdown body — the actual instruction injected into the prompt. */\n body: string;\n /** Where this rule applies. */\n scope: RuleScope;\n /** Whether the rule is currently active. */\n state: RuleState;\n /** Optional tags for grouping (\"testing\", \"style\", \"security\"). */\n tags?: string[];\n /** Modes this rule applies to. Omit / empty = global (every mode). */\n modes?: Mode[];\n /** ISO timestamp / friendly label of last edit. */\n updatedAt?: string;\n}\n"
13
+ }
14
+ ],
15
+ "registryDependencies": [
16
+ "https://usetheodev.github.io/theo-ui/r/mode-types.json"
17
+ ]
18
+ }