poetry-ui 0.0.2

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 (442) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +6 -0
  3. data/CHANGELOG.md +3 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +87 -0
  6. data/THIRD_PARTY_NOTICES.md +201 -0
  7. data/app/components/poetry/ui/accordion/component.html.erb +3 -0
  8. data/app/components/poetry/ui/accordion/component.rb +197 -0
  9. data/app/components/poetry/ui/accordion/preview.rb +39 -0
  10. data/app/components/poetry/ui/accordion/style.rb +37 -0
  11. data/app/components/poetry/ui/alert/component.html.erb +6 -0
  12. data/app/components/poetry/ui/alert/component.rb +61 -0
  13. data/app/components/poetry/ui/alert/preview.rb +35 -0
  14. data/app/components/poetry/ui/alert/style.rb +24 -0
  15. data/app/components/poetry/ui/alert_dialog/component.html.erb +17 -0
  16. data/app/components/poetry/ui/alert_dialog/component.rb +208 -0
  17. data/app/components/poetry/ui/alert_dialog/preview.rb +48 -0
  18. data/app/components/poetry/ui/alert_dialog/style.rb +46 -0
  19. data/app/components/poetry/ui/aspect_ratio/component.rb +63 -0
  20. data/app/components/poetry/ui/aspect_ratio/preview.rb +29 -0
  21. data/app/components/poetry/ui/aspect_ratio/style.rb +13 -0
  22. data/app/components/poetry/ui/attachment/component.html.erb +13 -0
  23. data/app/components/poetry/ui/attachment/component.rb +139 -0
  24. data/app/components/poetry/ui/attachment/preview.rb +83 -0
  25. data/app/components/poetry/ui/attachment/style.rb +59 -0
  26. data/app/components/poetry/ui/autocomplete/component.html.erb +16 -0
  27. data/app/components/poetry/ui/autocomplete/component.rb +193 -0
  28. data/app/components/poetry/ui/autocomplete/preview.rb +39 -0
  29. data/app/components/poetry/ui/autocomplete/style.rb +18 -0
  30. data/app/components/poetry/ui/avatar/component.rb +103 -0
  31. data/app/components/poetry/ui/avatar/group_preview.html.erb +11 -0
  32. data/app/components/poetry/ui/avatar/preview.rb +50 -0
  33. data/app/components/poetry/ui/avatar/style.rb +34 -0
  34. data/app/components/poetry/ui/badge/component.rb +73 -0
  35. data/app/components/poetry/ui/badge/preview.rb +54 -0
  36. data/app/components/poetry/ui/badge/style.rb +35 -0
  37. data/app/components/poetry/ui/breadcrumb/component.html.erb +32 -0
  38. data/app/components/poetry/ui/breadcrumb/component.rb +102 -0
  39. data/app/components/poetry/ui/breadcrumb/dropdown_crumb_preview.html.erb +21 -0
  40. data/app/components/poetry/ui/breadcrumb/preview.rb +45 -0
  41. data/app/components/poetry/ui/breadcrumb/style.rb +25 -0
  42. data/app/components/poetry/ui/bubble/component.html.erb +4 -0
  43. data/app/components/poetry/ui/bubble/component.rb +106 -0
  44. data/app/components/poetry/ui/bubble/preview.rb +62 -0
  45. data/app/components/poetry/ui/bubble/style.rb +47 -0
  46. data/app/components/poetry/ui/button/component.html.erb +14 -0
  47. data/app/components/poetry/ui/button/component.rb +168 -0
  48. data/app/components/poetry/ui/button/preview.rb +94 -0
  49. data/app/components/poetry/ui/button/style.rb +41 -0
  50. data/app/components/poetry/ui/button_group/component.rb +70 -0
  51. data/app/components/poetry/ui/button_group/popup_triggers_preview.html.erb +21 -0
  52. data/app/components/poetry/ui/button_group/preview.rb +46 -0
  53. data/app/components/poetry/ui/button_group/style.rb +27 -0
  54. data/app/components/poetry/ui/button_group/text_prefix_preview.html.erb +7 -0
  55. data/app/components/poetry/ui/calendar/component.html.erb +72 -0
  56. data/app/components/poetry/ui/calendar/component.rb +366 -0
  57. data/app/components/poetry/ui/calendar/preview.rb +55 -0
  58. data/app/components/poetry/ui/calendar/style.rb +55 -0
  59. data/app/components/poetry/ui/card/component.html.erb +11 -0
  60. data/app/components/poetry/ui/card/component.rb +90 -0
  61. data/app/components/poetry/ui/card/preview.rb +29 -0
  62. data/app/components/poetry/ui/card/style.rb +23 -0
  63. data/app/components/poetry/ui/carousel/component.html.erb +17 -0
  64. data/app/components/poetry/ui/carousel/component.rb +179 -0
  65. data/app/components/poetry/ui/carousel/preview.rb +67 -0
  66. data/app/components/poetry/ui/carousel/style.rb +39 -0
  67. data/app/components/poetry/ui/checkbox/component.html.erb +22 -0
  68. data/app/components/poetry/ui/checkbox/component.rb +179 -0
  69. data/app/components/poetry/ui/checkbox/preview.rb +68 -0
  70. data/app/components/poetry/ui/checkbox/style.rb +28 -0
  71. data/app/components/poetry/ui/clipboard_text/component.html.erb +15 -0
  72. data/app/components/poetry/ui/clipboard_text/component.rb +154 -0
  73. data/app/components/poetry/ui/clipboard_text/preview.rb +24 -0
  74. data/app/components/poetry/ui/clipboard_text/style.rb +23 -0
  75. data/app/components/poetry/ui/code_block/component.html.erb +13 -0
  76. data/app/components/poetry/ui/code_block/component.rb +141 -0
  77. data/app/components/poetry/ui/code_block/preview.rb +40 -0
  78. data/app/components/poetry/ui/code_block/style.rb +38 -0
  79. data/app/components/poetry/ui/collapsible/component.html.erb +4 -0
  80. data/app/components/poetry/ui/collapsible/component.rb +134 -0
  81. data/app/components/poetry/ui/collapsible/preview.rb +24 -0
  82. data/app/components/poetry/ui/combobox/component.html.erb +49 -0
  83. data/app/components/poetry/ui/combobox/component.rb +1086 -0
  84. data/app/components/poetry/ui/combobox/preview.rb +164 -0
  85. data/app/components/poetry/ui/combobox/style.rb +148 -0
  86. data/app/components/poetry/ui/command/component.html.erb +20 -0
  87. data/app/components/poetry/ui/command/component.rb +526 -0
  88. data/app/components/poetry/ui/command/dialog_component.html.erb +14 -0
  89. data/app/components/poetry/ui/command/dialog_component.rb +206 -0
  90. data/app/components/poetry/ui/command/dialog_preview.rb +39 -0
  91. data/app/components/poetry/ui/command/preview.rb +121 -0
  92. data/app/components/poetry/ui/command/style.rb +78 -0
  93. data/app/components/poetry/ui/composable_trigger.rb +63 -0
  94. data/app/components/poetry/ui/context_menu/component.html.erb +6 -0
  95. data/app/components/poetry/ui/context_menu/component.rb +358 -0
  96. data/app/components/poetry/ui/context_menu/preview.rb +111 -0
  97. data/app/components/poetry/ui/context_menu/style.rb +59 -0
  98. data/app/components/poetry/ui/data_table/component.html.erb +78 -0
  99. data/app/components/poetry/ui/data_table/component.rb +277 -0
  100. data/app/components/poetry/ui/data_table/preview.rb +68 -0
  101. data/app/components/poetry/ui/data_table/state.rb +115 -0
  102. data/app/components/poetry/ui/data_table/style.rb +42 -0
  103. data/app/components/poetry/ui/date_field/component.html.erb +7 -0
  104. data/app/components/poetry/ui/date_field/component.rb +209 -0
  105. data/app/components/poetry/ui/date_field/preview.rb +42 -0
  106. data/app/components/poetry/ui/date_field/style.rb +35 -0
  107. data/app/components/poetry/ui/date_picker/component.html.erb +23 -0
  108. data/app/components/poetry/ui/date_picker/component.rb +242 -0
  109. data/app/components/poetry/ui/date_picker/preview.rb +31 -0
  110. data/app/components/poetry/ui/date_picker/style.rb +25 -0
  111. data/app/components/poetry/ui/deferred/component.html.erb +16 -0
  112. data/app/components/poetry/ui/deferred/component.rb +90 -0
  113. data/app/components/poetry/ui/deferred/preview.rb +24 -0
  114. data/app/components/poetry/ui/dialog/component.html.erb +21 -0
  115. data/app/components/poetry/ui/dialog/component.rb +194 -0
  116. data/app/components/poetry/ui/dialog/preview.rb +33 -0
  117. data/app/components/poetry/ui/dialog/style.rb +30 -0
  118. data/app/components/poetry/ui/drawer/component.html.erb +16 -0
  119. data/app/components/poetry/ui/drawer/component.rb +207 -0
  120. data/app/components/poetry/ui/drawer/preview.rb +69 -0
  121. data/app/components/poetry/ui/drawer/style.rb +105 -0
  122. data/app/components/poetry/ui/dropdown_menu/component.html.erb +6 -0
  123. data/app/components/poetry/ui/dropdown_menu/component.rb +350 -0
  124. data/app/components/poetry/ui/dropdown_menu/preview.rb +101 -0
  125. data/app/components/poetry/ui/dropdown_menu/style.rb +60 -0
  126. data/app/components/poetry/ui/empty/component.html.erb +10 -0
  127. data/app/components/poetry/ui/empty/component.rb +79 -0
  128. data/app/components/poetry/ui/empty/preview.rb +27 -0
  129. data/app/components/poetry/ui/empty/style.rb +27 -0
  130. data/app/components/poetry/ui/family_identity.rb +52 -0
  131. data/app/components/poetry/ui/field/component.html.erb +19 -0
  132. data/app/components/poetry/ui/field/component.rb +183 -0
  133. data/app/components/poetry/ui/field/preview.html.erb +30 -0
  134. data/app/components/poetry/ui/field/preview.rb +72 -0
  135. data/app/components/poetry/ui/field/style.rb +68 -0
  136. data/app/components/poetry/ui/field_group/component.rb +61 -0
  137. data/app/components/poetry/ui/field_group/preview.html.erb +30 -0
  138. data/app/components/poetry/ui/field_group/preview.rb +20 -0
  139. data/app/components/poetry/ui/field_group/style.rb +25 -0
  140. data/app/components/poetry/ui/field_separator/component.rb +70 -0
  141. data/app/components/poetry/ui/field_separator/preview.rb +18 -0
  142. data/app/components/poetry/ui/field_separator/style.rb +20 -0
  143. data/app/components/poetry/ui/fieldset/component.rb +90 -0
  144. data/app/components/poetry/ui/fieldset/preview.html.erb +22 -0
  145. data/app/components/poetry/ui/fieldset/preview.rb +25 -0
  146. data/app/components/poetry/ui/fieldset/style.rb +18 -0
  147. data/app/components/poetry/ui/file_input/component.html.erb +14 -0
  148. data/app/components/poetry/ui/file_input/component.rb +188 -0
  149. data/app/components/poetry/ui/file_input/preview.rb +30 -0
  150. data/app/components/poetry/ui/file_input/style.rb +35 -0
  151. data/app/components/poetry/ui/hover_card/component.html.erb +6 -0
  152. data/app/components/poetry/ui/hover_card/component.rb +201 -0
  153. data/app/components/poetry/ui/hover_card/preview.rb +60 -0
  154. data/app/components/poetry/ui/hover_card/style.rb +17 -0
  155. data/app/components/poetry/ui/icon/component.rb +127 -0
  156. data/app/components/poetry/ui/icon/preview.rb +21 -0
  157. data/app/components/poetry/ui/input/component.rb +80 -0
  158. data/app/components/poetry/ui/input/preview.rb +35 -0
  159. data/app/components/poetry/ui/input/style.rb +18 -0
  160. data/app/components/poetry/ui/input_group/button_preview.html.erb +12 -0
  161. data/app/components/poetry/ui/input_group/component.rb +69 -0
  162. data/app/components/poetry/ui/input_group/preview.rb +23 -0
  163. data/app/components/poetry/ui/input_group/search_preview.html.erb +13 -0
  164. data/app/components/poetry/ui/input_group/style.rb +40 -0
  165. data/app/components/poetry/ui/input_group/textarea_preview.html.erb +10 -0
  166. data/app/components/poetry/ui/input_group_field.rb +61 -0
  167. data/app/components/poetry/ui/input_otp/component.html.erb +25 -0
  168. data/app/components/poetry/ui/input_otp/component.rb +250 -0
  169. data/app/components/poetry/ui/input_otp/preview.rb +91 -0
  170. data/app/components/poetry/ui/input_otp/style.rb +36 -0
  171. data/app/components/poetry/ui/item/component.html.erb +13 -0
  172. data/app/components/poetry/ui/item/component.rb +99 -0
  173. data/app/components/poetry/ui/item/group_preview.html.erb +15 -0
  174. data/app/components/poetry/ui/item/preview.rb +52 -0
  175. data/app/components/poetry/ui/item/style.rb +45 -0
  176. data/app/components/poetry/ui/kbd/component.rb +47 -0
  177. data/app/components/poetry/ui/kbd/group_preview.html.erb +4 -0
  178. data/app/components/poetry/ui/kbd/preview.rb +23 -0
  179. data/app/components/poetry/ui/kbd/style.rb +13 -0
  180. data/app/components/poetry/ui/label/component.rb +46 -0
  181. data/app/components/poetry/ui/label/preview.html.erb +5 -0
  182. data/app/components/poetry/ui/label/preview.rb +16 -0
  183. data/app/components/poetry/ui/label/style.rb +13 -0
  184. data/app/components/poetry/ui/link/component.rb +62 -0
  185. data/app/components/poetry/ui/link/preview.rb +31 -0
  186. data/app/components/poetry/ui/link/style.rb +20 -0
  187. data/app/components/poetry/ui/marker/component.html.erb +4 -0
  188. data/app/components/poetry/ui/marker/component.rb +81 -0
  189. data/app/components/poetry/ui/marker/preview.rb +43 -0
  190. data/app/components/poetry/ui/marker/style.rb +24 -0
  191. data/app/components/poetry/ui/menubar/component.html.erb +3 -0
  192. data/app/components/poetry/ui/menubar/component.rb +493 -0
  193. data/app/components/poetry/ui/menubar/preview.rb +164 -0
  194. data/app/components/poetry/ui/menubar/style.rb +71 -0
  195. data/app/components/poetry/ui/menus.rb +386 -0
  196. data/app/components/poetry/ui/message/component.html.erb +8 -0
  197. data/app/components/poetry/ui/message/component.rb +64 -0
  198. data/app/components/poetry/ui/message/preview.rb +33 -0
  199. data/app/components/poetry/ui/message/style.rb +29 -0
  200. data/app/components/poetry/ui/message_scroller/component.html.erb +15 -0
  201. data/app/components/poetry/ui/message_scroller/component.rb +163 -0
  202. data/app/components/poetry/ui/message_scroller/preview.rb +33 -0
  203. data/app/components/poetry/ui/message_scroller/style.rb +39 -0
  204. data/app/components/poetry/ui/metadata_list/component.rb +88 -0
  205. data/app/components/poetry/ui/metadata_list/preview.rb +55 -0
  206. data/app/components/poetry/ui/metadata_list/style.rb +35 -0
  207. data/app/components/poetry/ui/meter/component.rb +117 -0
  208. data/app/components/poetry/ui/meter/preview.rb +28 -0
  209. data/app/components/poetry/ui/meter/style.rb +16 -0
  210. data/app/components/poetry/ui/native_select/component.rb +124 -0
  211. data/app/components/poetry/ui/native_select/labelled_preview.html.erb +7 -0
  212. data/app/components/poetry/ui/native_select/preview.rb +20 -0
  213. data/app/components/poetry/ui/native_select/style.rb +22 -0
  214. data/app/components/poetry/ui/navigation_menu/bar_preview.html.erb +14 -0
  215. data/app/components/poetry/ui/navigation_menu/component.html.erb +25 -0
  216. data/app/components/poetry/ui/navigation_menu/component.rb +254 -0
  217. data/app/components/poetry/ui/navigation_menu/preview.rb +20 -0
  218. data/app/components/poetry/ui/navigation_menu/style.rb +73 -0
  219. data/app/components/poetry/ui/navigation_menu/viewport_preview.html.erb +23 -0
  220. data/app/components/poetry/ui/number_field/component.html.erb +19 -0
  221. data/app/components/poetry/ui/number_field/component.rb +263 -0
  222. data/app/components/poetry/ui/number_field/preview.rb +66 -0
  223. data/app/components/poetry/ui/number_field/style.rb +14 -0
  224. data/app/components/poetry/ui/pagination/component.html.erb +40 -0
  225. data/app/components/poetry/ui/pagination/component.rb +182 -0
  226. data/app/components/poetry/ui/pagination/preview.rb +47 -0
  227. data/app/components/poetry/ui/pagination/style.rb +27 -0
  228. data/app/components/poetry/ui/popover/component.html.erb +17 -0
  229. data/app/components/poetry/ui/popover/component.rb +203 -0
  230. data/app/components/poetry/ui/popover/preview.html.erb +29 -0
  231. data/app/components/poetry/ui/popover/preview.rb +66 -0
  232. data/app/components/poetry/ui/popover/style.rb +26 -0
  233. data/app/components/poetry/ui/popper_consumer.rb +69 -0
  234. data/app/components/poetry/ui/progress/component.rb +95 -0
  235. data/app/components/poetry/ui/progress/preview.rb +22 -0
  236. data/app/components/poetry/ui/progress/style.rb +19 -0
  237. data/app/components/poetry/ui/questionnaire/component.html.erb +80 -0
  238. data/app/components/poetry/ui/questionnaire/component.rb +396 -0
  239. data/app/components/poetry/ui/questionnaire/preview.rb +99 -0
  240. data/app/components/poetry/ui/questionnaire/style.rb +53 -0
  241. data/app/components/poetry/ui/radio_group/component.html.erb +4 -0
  242. data/app/components/poetry/ui/radio_group/component.rb +323 -0
  243. data/app/components/poetry/ui/radio_group/preview.rb +109 -0
  244. data/app/components/poetry/ui/radio_group/style.rb +53 -0
  245. data/app/components/poetry/ui/resizable/component.html.erb +10 -0
  246. data/app/components/poetry/ui/resizable/component.rb +178 -0
  247. data/app/components/poetry/ui/resizable/preview.rb +42 -0
  248. data/app/components/poetry/ui/resizable/style.rb +30 -0
  249. data/app/components/poetry/ui/scroll_area/component.rb +76 -0
  250. data/app/components/poetry/ui/scroll_area/preview.rb +35 -0
  251. data/app/components/poetry/ui/scroll_area/style.rb +19 -0
  252. data/app/components/poetry/ui/search_field/component.html.erb +15 -0
  253. data/app/components/poetry/ui/search_field/component.rb +137 -0
  254. data/app/components/poetry/ui/search_field/preview.rb +23 -0
  255. data/app/components/poetry/ui/search_field/style.rb +18 -0
  256. data/app/components/poetry/ui/select/component.html.erb +15 -0
  257. data/app/components/poetry/ui/select/component.rb +639 -0
  258. data/app/components/poetry/ui/select/preview.rb +113 -0
  259. data/app/components/poetry/ui/select/style.rb +58 -0
  260. data/app/components/poetry/ui/sensitive_input/component.html.erb +29 -0
  261. data/app/components/poetry/ui/sensitive_input/component.rb +274 -0
  262. data/app/components/poetry/ui/sensitive_input/preview.rb +40 -0
  263. data/app/components/poetry/ui/sensitive_input/style.rb +49 -0
  264. data/app/components/poetry/ui/separator/component.rb +57 -0
  265. data/app/components/poetry/ui/separator/preview.rb +19 -0
  266. data/app/components/poetry/ui/separator/style.rb +15 -0
  267. data/app/components/poetry/ui/sheet/component.html.erb +21 -0
  268. data/app/components/poetry/ui/sheet/component.rb +97 -0
  269. data/app/components/poetry/ui/sheet/preview.rb +57 -0
  270. data/app/components/poetry/ui/sheet/style.rb +47 -0
  271. data/app/components/poetry/ui/sidebar/component.html.erb +20 -0
  272. data/app/components/poetry/ui/sidebar/component.rb +249 -0
  273. data/app/components/poetry/ui/sidebar/preview.rb +21 -0
  274. data/app/components/poetry/ui/sidebar/shell_preview.html.erb +47 -0
  275. data/app/components/poetry/ui/sidebar/style.rb +139 -0
  276. data/app/components/poetry/ui/skeleton/component.rb +39 -0
  277. data/app/components/poetry/ui/skeleton/preview.rb +22 -0
  278. data/app/components/poetry/ui/skeleton/style.rb +13 -0
  279. data/app/components/poetry/ui/slider/component.html.erb +13 -0
  280. data/app/components/poetry/ui/slider/component.rb +360 -0
  281. data/app/components/poetry/ui/slider/preview.rb +73 -0
  282. data/app/components/poetry/ui/slider/style.rb +55 -0
  283. data/app/components/poetry/ui/spinner/component.rb +58 -0
  284. data/app/components/poetry/ui/spinner/preview.rb +22 -0
  285. data/app/components/poetry/ui/spinner/style.rb +12 -0
  286. data/app/components/poetry/ui/stat/component.html.erb +13 -0
  287. data/app/components/poetry/ui/stat/component.rb +102 -0
  288. data/app/components/poetry/ui/stat/preview.rb +47 -0
  289. data/app/components/poetry/ui/stat/style.rb +29 -0
  290. data/app/components/poetry/ui/switch/component.html.erb +15 -0
  291. data/app/components/poetry/ui/switch/component.rb +136 -0
  292. data/app/components/poetry/ui/switch/preview.rb +66 -0
  293. data/app/components/poetry/ui/switch/style.rb +22 -0
  294. data/app/components/poetry/ui/table/component.html.erb +3 -0
  295. data/app/components/poetry/ui/table/component.rb +93 -0
  296. data/app/components/poetry/ui/table/preview.rb +96 -0
  297. data/app/components/poetry/ui/table/style.rb +26 -0
  298. data/app/components/poetry/ui/tabs/component.html.erb +10 -0
  299. data/app/components/poetry/ui/tabs/component.rb +253 -0
  300. data/app/components/poetry/ui/tabs/preview.rb +43 -0
  301. data/app/components/poetry/ui/tabs/style.rb +49 -0
  302. data/app/components/poetry/ui/tag_group/component.html.erb +8 -0
  303. data/app/components/poetry/ui/tag_group/component.rb +203 -0
  304. data/app/components/poetry/ui/tag_group/preview.rb +43 -0
  305. data/app/components/poetry/ui/tag_group/style.rb +29 -0
  306. data/app/components/poetry/ui/textarea/component.rb +64 -0
  307. data/app/components/poetry/ui/textarea/preview.rb +75 -0
  308. data/app/components/poetry/ui/textarea/style.rb +18 -0
  309. data/app/components/poetry/ui/time_field/component.html.erb +6 -0
  310. data/app/components/poetry/ui/time_field/component.rb +85 -0
  311. data/app/components/poetry/ui/time_field/preview.rb +40 -0
  312. data/app/components/poetry/ui/time_field/style.rb +30 -0
  313. data/app/components/poetry/ui/timeline/component.rb +134 -0
  314. data/app/components/poetry/ui/timeline/preview.rb +61 -0
  315. data/app/components/poetry/ui/timeline/style.rb +54 -0
  316. data/app/components/poetry/ui/toast/component.html.erb +24 -0
  317. data/app/components/poetry/ui/toast/component.rb +178 -0
  318. data/app/components/poetry/ui/toast/preview.rb +69 -0
  319. data/app/components/poetry/ui/toast/style.rb +33 -0
  320. data/app/components/poetry/ui/toast_trigger/addressed_preview.html.erb +12 -0
  321. data/app/components/poetry/ui/toast_trigger/component.html.erb +3 -0
  322. data/app/components/poetry/ui/toast_trigger/component.rb +82 -0
  323. data/app/components/poetry/ui/toast_trigger/default_preview.html.erb +11 -0
  324. data/app/components/poetry/ui/toast_trigger/preview.rb +19 -0
  325. data/app/components/poetry/ui/toaster/component.html.erb +1 -0
  326. data/app/components/poetry/ui/toaster/component.rb +99 -0
  327. data/app/components/poetry/ui/toaster/positions_preview.html.erb +24 -0
  328. data/app/components/poetry/ui/toaster/preview.rb +46 -0
  329. data/app/components/poetry/ui/toaster/stamp_preview.html.erb +15 -0
  330. data/app/components/poetry/ui/toaster/style.rb +26 -0
  331. data/app/components/poetry/ui/toggle/component.rb +120 -0
  332. data/app/components/poetry/ui/toggle/preview.rb +79 -0
  333. data/app/components/poetry/ui/toggle/style.rb +33 -0
  334. data/app/components/poetry/ui/toggle_group/component.html.erb +4 -0
  335. data/app/components/poetry/ui/toggle_group/component.rb +252 -0
  336. data/app/components/poetry/ui/toggle_group/preview.rb +143 -0
  337. data/app/components/poetry/ui/toggle_group/style.rb +33 -0
  338. data/app/components/poetry/ui/toolbar/component.rb +125 -0
  339. data/app/components/poetry/ui/toolbar/preview.rb +41 -0
  340. data/app/components/poetry/ui/toolbar/style.rb +20 -0
  341. data/app/components/poetry/ui/tooltip/component.html.erb +20 -0
  342. data/app/components/poetry/ui/tooltip/component.rb +185 -0
  343. data/app/components/poetry/ui/tooltip/preview.rb +74 -0
  344. data/app/components/poetry/ui/tooltip/previews/provider_row.html.erb +25 -0
  345. data/app/components/poetry/ui/tooltip/style.rb +24 -0
  346. data/app/components/poetry/ui/tree/component.html.erb +32 -0
  347. data/app/components/poetry/ui/tree/component.rb +233 -0
  348. data/app/components/poetry/ui/tree/preview.rb +55 -0
  349. data/app/components/poetry/ui/tree/style.rb +29 -0
  350. data/app/components/poetry/ui/typeset/component.rb +73 -0
  351. data/app/components/poetry/ui/typeset/preview.rb +45 -0
  352. data/app/components/poetry/ui/typeset/style.rb +15 -0
  353. data/app/controllers/poetry/ui/llms_controller.rb +28 -0
  354. data/app/helpers/poetry/ui/components_helper.rb +1794 -0
  355. data/app/helpers/poetry/ui/form_builder/type_inference.rb +117 -0
  356. data/app/helpers/poetry/ui/form_builder.rb +916 -0
  357. data/app/helpers/poetry/ui/optimistic_form_builder.rb +55 -0
  358. data/app/helpers/poetry/ui/toast_stream_actions.rb +27 -0
  359. data/config/component_descriptions.yml +96 -0
  360. data/config/component_registry.yml +12257 -0
  361. data/config/locales/en.yml +93 -0
  362. data/config/routes.rb +8 -0
  363. data/config/template_classes.txt +113 -0
  364. data/lib/generators/poetry/add/add_generator.rb +218 -0
  365. data/lib/generators/poetry/agent_rules/agent_rules_generator.rb +91 -0
  366. data/lib/generators/poetry/agents/agents_generator.rb +24 -0
  367. data/lib/generators/poetry/agents_section.rb +148 -0
  368. data/lib/generators/poetry/block/block_generator.rb +67 -0
  369. data/lib/generators/poetry/block/templates/action_bar.html.erb +45 -0
  370. data/lib/generators/poetry/block/templates/app_shell.html.erb +139 -0
  371. data/lib/generators/poetry/block/templates/data_index.html.erb +85 -0
  372. data/lib/generators/poetry/block/templates/destructive_panel.html.erb +26 -0
  373. data/lib/generators/poetry/block/templates/page_header.html.erb +25 -0
  374. data/lib/generators/poetry/block/templates/section_card.html.erb +42 -0
  375. data/lib/generators/poetry/block/templates/stepper.html.erb +79 -0
  376. data/lib/generators/poetry/block/templates/top_nav.html.erb +44 -0
  377. data/lib/generators/poetry/diff/diff_generator.rb +135 -0
  378. data/lib/generators/poetry/editor/editor_generator.rb +344 -0
  379. data/lib/generators/poetry/editor/templates/poetry-check-hook +81 -0
  380. data/lib/generators/poetry/install/install_generator.rb +364 -0
  381. data/lib/generators/poetry/pagination/pagination_generator.rb +76 -0
  382. data/lib/generators/poetry/pagination/templates/kaminari_paginator.html.erb +29 -0
  383. data/lib/generators/poetry/pagination/templates/poetry_link_renderer.rb +34 -0
  384. data/lib/generators/poetry/pagination/templates/poetry_pagy_helper.rb +28 -0
  385. data/lib/generators/poetry/recipes/agent_embed/app/javascript/controllers/agent_demo_controller.js +131 -0
  386. data/lib/generators/poetry/recipes/screen_data_index/app/controllers/orders_controller.rb +15 -0
  387. data/lib/generators/poetry/recipes/screen_data_index/app/views/orders/index.html.erb +7 -0
  388. data/lib/generators/poetry/recipes/screen_data_index/test/system/orders_index_test.rb +16 -0
  389. data/lib/generators/poetry/recipes/screen_settings/app/controllers/settings_controller.rb +14 -0
  390. data/lib/generators/poetry/recipes/screen_settings/app/views/settings/show.html.erb +9 -0
  391. data/lib/generators/poetry/recipes/screen_settings/test/system/settings_show_test.rb +16 -0
  392. data/lib/generators/poetry/scaffold_templates/scaffold_templates_generator.rb +60 -0
  393. data/lib/generators/poetry/scaffold_templates/templates/_form.html.erb.tt +77 -0
  394. data/lib/generators/poetry/scaffold_templates/templates/controller.rb.tt +78 -0
  395. data/lib/generators/poetry/scaffold_templates/templates/edit.html.erb.tt +14 -0
  396. data/lib/generators/poetry/scaffold_templates/templates/index.html.erb.tt +24 -0
  397. data/lib/generators/poetry/scaffold_templates/templates/new.html.erb.tt +11 -0
  398. data/lib/generators/poetry/scaffold_templates/templates/partial.html.erb.tt +13 -0
  399. data/lib/generators/poetry/scaffold_templates/templates/show.html.erb.tt +18 -0
  400. data/lib/generators/poetry/skill/skill_generator.rb +34 -0
  401. data/lib/generators/poetry/skill/templates/poetry-component/SKILL.md +58 -0
  402. data/lib/generators/poetry/skill/templates/poetry-component/references/anatomy.md +152 -0
  403. data/lib/generators/poetry/skill/templates/poetry-component/references/checklist.md +67 -0
  404. data/lib/generators/poetry/skill/templates/poetry-component/references/documentation.md +137 -0
  405. data/lib/generators/poetry/skill/templates/poetry-design/SKILL.md +56 -0
  406. data/lib/generators/poetry/skill/templates/poetry-design/references/audit.md +123 -0
  407. data/lib/generators/poetry/skill/templates/poetry-design/references/compose.md +73 -0
  408. data/lib/generators/poetry/skill/templates/poetry-design/references/figma.md +68 -0
  409. data/lib/generators/poetry/skill/templates/poetry-design/references/paper.md +56 -0
  410. data/lib/generators/poetry/skill/templates/poetry-design/references/study.md +47 -0
  411. data/lib/generators/poetry/skill/templates/poetry-design/references/theme.md +70 -0
  412. data/lib/generators/poetry/skills_section.rb +58 -0
  413. data/lib/poetry/ui/chat.rb +283 -0
  414. data/lib/poetry/ui/code_block_highlighter.rb +60 -0
  415. data/lib/poetry/ui/engine.rb +55 -0
  416. data/lib/poetry/ui/recipes.rb +157 -0
  417. data/lib/poetry/ui/testing/combobox.rb +167 -0
  418. data/lib/poetry/ui/testing/dialog.rb +73 -0
  419. data/lib/poetry/ui/testing/menu.rb +127 -0
  420. data/lib/poetry/ui/testing/registration.rb +58 -0
  421. data/lib/poetry/ui/testing/select.rb +147 -0
  422. data/lib/poetry/ui/testing/tester.rb +130 -0
  423. data/lib/poetry/ui/testing.rb +130 -0
  424. data/lib/poetry/ui/themes.rb +109 -0
  425. data/lib/poetry/ui/version.rb +8 -0
  426. data/lib/poetry/ui/webmcp.rb +91 -0
  427. data/lib/poetry/ui.rb +285 -0
  428. data/lib/poetry-ui.rb +5 -0
  429. data/lib/tasks/poetry/check.rake +73 -0
  430. data/lib/tasks/poetry/design.rake +293 -0
  431. data/lib/tasks/poetry/verify.rake +36 -0
  432. data/themes/default.css +1793 -0
  433. data/themes/luma.css +1764 -0
  434. data/themes/lyra.css +1768 -0
  435. data/themes/maia.css +1768 -0
  436. data/themes/mira.css +1760 -0
  437. data/themes/nova.css +1755 -0
  438. data/themes/rhea.css +1771 -0
  439. data/themes/sera.css +1757 -0
  440. data/themes/vega.css +1775 -0
  441. data/typeset/typeset.css +499 -0
  442. metadata +502 -0
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Testing
6
+ # The Select interaction contract, executable: open lands focus on
7
+ # the SELECTED option (unlike the menu family, which moves
8
+ # data-highlighted), committing syncs the native <select> and closes
9
+ # with focus back on the trigger, and the closed trigger typeaheads
10
+ # without opening (native parity). Asserts ride
11
+ # data-open/aria-expanded/the native
12
+ # value - the public attribute contract, not markup internals.
13
+ #
14
+ # CONTENT RESOLVES THROUGH THE ID PAIR, document-wide: portal-on-open
15
+ # moves the open listbox to body (docs/portal-on-open.md), so root
16
+ # scoping stops holding for content and its items - the trigger's
17
+ # aria-controls id is the production controllers' own resolution
18
+ # rule, and id-anchored selectors keep Capybara's waiting semantics.
19
+ #
20
+ # @example Committing an option through the real keyboard path
21
+ # select = poetry_select("#plan")
22
+ # select.select_option("Pro", via: :keyboard)
23
+ # assert_equal "pro", select.value
24
+ class Select < Tester
25
+ # Whether the listbox is currently open.
26
+ #
27
+ # @return [Boolean]
28
+ def open?(wait: 0)
29
+ session.has_selector?("##{content_id}[data-open]", visible: :all, wait: wait)
30
+ rescue Capybara::ElementNotFound
31
+ false
32
+ end
33
+
34
+ # Opens the listbox (no-op when already open). via: :keyboard
35
+ # focuses the trigger and presses ArrowDown; :mouse presses it.
36
+ #
37
+ # @return [Select] self
38
+ def open(via: :mouse)
39
+ return self if open?
40
+
41
+ case via
42
+ when :keyboard
43
+ focus(trigger)
44
+ keys(:down)
45
+ else
46
+ press(trigger)
47
+ end
48
+
49
+ session.assert_selector("##{content_id}[data-slot='select-content'][data-open][data-side]")
50
+ self
51
+ end
52
+
53
+ # Escape-closes the open listbox and waits for the closed state.
54
+ #
55
+ # @return [Select] self
56
+ def close
57
+ keys(:escape) if open?
58
+ session.assert_selector("##{content_id}[data-closed]", visible: :all)
59
+ self
60
+ end
61
+
62
+ # Opens first when closed; exact visible-text match; waits for the
63
+ # commit to land on the native select before returning.
64
+ #
65
+ # @return [Select] self
66
+ def select_option(text, via: :mouse)
67
+ self.open(via: via)
68
+
69
+ if via == :keyboard
70
+ walk_highlight_to(text)
71
+ keys(:enter)
72
+ else
73
+ press(option(text))
74
+ end
75
+
76
+ session.assert_selector("##{content_id}[data-closed]", visible: :all)
77
+ self
78
+ end
79
+
80
+ # The submitted value - the native <select> is the serialization
81
+ # truth, so this is what the server would receive.
82
+ #
83
+ # @return [String]
84
+ def value
85
+ hidden_part("select-native").value
86
+ end
87
+
88
+ # The trigger's visible value text.
89
+ #
90
+ # @return [String]
91
+ def text
92
+ part("select-value").text
93
+ end
94
+
95
+ # The visible option texts (opens the listbox first when closed).
96
+ #
97
+ # @return [Array<String>]
98
+ def options
99
+ open unless open?
100
+ content.all("[data-slot='select-item']").map(&:text)
101
+ end
102
+
103
+ private
104
+
105
+ # Bounded highlight walk: at most one pass over the items - a
106
+ # missing/mistyped label raises instead of arrowing forever.
107
+ def walk_highlight_to(text)
108
+ items = content.all("[data-slot='select-item']")
109
+
110
+ (items.size + 1).times do
111
+ return if highlighted_text == text
112
+
113
+ keys(:down)
114
+ end
115
+
116
+ raise Capybara::ElementNotFound,
117
+ "no option #{text.inspect} reached by ArrowDown - options: #{items.map(&:text).inspect}"
118
+ end
119
+
120
+ def trigger
121
+ part("select-trigger")
122
+ end
123
+
124
+ # The trigger's aria-controls names the listbox wherever it sits.
125
+ def content_id
126
+ @content_id ||= trigger["aria-controls"]
127
+ end
128
+
129
+ def content
130
+ session.find("##{content_id}", visible: :all)
131
+ end
132
+
133
+ def option(text)
134
+ content.find("[data-slot='select-item']", text: text, exact_text: true)
135
+ end
136
+
137
+ # Select's active option is REAL FOCUS (the menu family marks
138
+ # data-highlighted instead).
139
+ def highlighted_text
140
+ content.find("[data-slot='select-item']:focus", wait: 1).text
141
+ rescue Capybara::ElementNotFound
142
+ nil
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module Testing
6
+ # The tester base: root resolution, part lookup by data-slot (the
7
+ # contract surface - never CSS classes), and Capybara-native waiting.
8
+ # Subclasses encode one component's real interaction sequences.
9
+ class Tester
10
+ # @return [Capybara::Session] the session driving the browser
11
+ attr_reader :session
12
+
13
+ def initialize(root, session:)
14
+ @session = session
15
+ @root_locator = root
16
+ end
17
+
18
+ # The component root node, re-found on every access so Turbo
19
+ # re-renders never leave the tester holding a stale element.
20
+ #
21
+ # @return [Capybara::Node::Element]
22
+ def root
23
+ case @root_locator
24
+ when String then session.find(@root_locator)
25
+ else @root_locator
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ # A part under the root, by its data-slot name. The error names
32
+ # the missing slot so a wrong root reads as "not a poetry_select",
33
+ # not as an opaque Capybara timeout.
34
+ def part(slot, **)
35
+ root.find(slot_selector(slot), **)
36
+ rescue Capybara::ElementNotFound
37
+ raise Capybara::ElementNotFound,
38
+ "no [data-slot=#{slot}] under #{describe_root} - is this the right component root?"
39
+ end
40
+
41
+ def part?(slot, **)
42
+ root.has_selector?(slot_selector(slot), **)
43
+ end
44
+
45
+ def parts(slot, **)
46
+ root.all(slot_selector(slot), **)
47
+ end
48
+
49
+ # Overlay content PORTALS to body while open (portal-on-open,
50
+ # docs/portal-on-open.md) - root-scoped lookups only hold for
51
+ # closed content and non-overlay parts. Component testers resolve
52
+ # open content document-wide through the trigger's aria-controls
53
+ # id (the production controllers' own rule); visibility flips via
54
+ # hidden/data-open, so look through visibility for closed content.
55
+ def hidden_part(slot)
56
+ root.find(slot_selector(slot), visible: :all)
57
+ end
58
+
59
+ def slot_selector(slot)
60
+ "[data-slot='#{slot}']"
61
+ end
62
+
63
+ def describe_root
64
+ @root_locator.is_a?(String) ? @root_locator.inspect : "the given node"
65
+ end
66
+
67
+ # Real keyboard focus WITHOUT a press (the Tab-arrival reality the
68
+ # keyboard path simulates) - a click would run the pointer seams.
69
+ def focus(node)
70
+ session.execute_script("arguments[0].focus()", node)
71
+ node
72
+ end
73
+
74
+ # A real pointer press. Under Cuprite the raw mouse is driven at
75
+ # the element's LIVE center (Cuprite's cached quads can disagree
76
+ # with the settled layout and hit-test the wrong point) - still a
77
+ # genuine CDP pointerdown/up/click sequence, never a synthetic
78
+ # dispatch. Other drivers take Capybara's own click.
79
+ def press(node)
80
+ mouse = session.driver.respond_to?(:browser) && session.driver.browser.respond_to?(:mouse) &&
81
+ session.driver.browser.mouse
82
+
83
+ if mouse
84
+ node.scroll_to(node) if node.respond_to?(:scroll_to)
85
+ x, y = session.evaluate_script(<<~JS, node)
86
+ (() => { const r = arguments[0].getBoundingClientRect();
87
+ return [r.x + r.width / 2, r.y + r.height / 2]; })()
88
+ JS
89
+ mouse.click(x: x, y: y)
90
+ else
91
+ node.click
92
+ end
93
+
94
+ node
95
+ end
96
+
97
+ # Keys go to whatever HAS focus (the keyboard reality). Under
98
+ # Cuprite the raw Ferrum keyboard types - pure key events with
99
+ # Ferrum's own simple names (:down, :enter, :escape); a Capybara
100
+ # NODE#send_keys would click-to-focus first, which runs the
101
+ # pointer seams the keyboard path exists to avoid.
102
+ def keys(*sequence)
103
+ keyboard = session.driver.respond_to?(:browser) &&
104
+ session.driver.browser.respond_to?(:keyboard) &&
105
+ session.driver.browser.keyboard
106
+
107
+ if keyboard
108
+ sequence.each { |key| keyboard.type(key) }
109
+ elsif session.respond_to?(:send_keys)
110
+ session.send_keys(*sequence)
111
+ else
112
+ active_element.send_keys(*sequence)
113
+ end
114
+ end
115
+
116
+ def active_element
117
+ session.evaluate_script("document.activeElement") if session.driver.respond_to?(:evaluate_script)
118
+ end
119
+
120
+ def wait_until(message)
121
+ Timeout.timeout(Capybara.default_max_wait_time) do
122
+ sleep 0.05 until yield
123
+ end
124
+ rescue Timeout::Error
125
+ raise Capybara::ExpectationNotMet, message
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "testing/tester"
4
+ require_relative "testing/select"
5
+ require_relative "testing/combobox"
6
+ require_relative "testing/menu"
7
+ require_relative "testing/dialog"
8
+ require_relative "testing/registration"
9
+
10
+ module Poetry
11
+ module Ui
12
+ # Consumer-facing interaction testers: drive poetry components
13
+ # through their REAL keyboard/pointer
14
+ # sequences in a Capybara system test and assert against the public
15
+ # attribute contract (data-open, aria-expanded, data-highlighted) -
16
+ # never against markup internals. Each tester is an executable spec of
17
+ # its component's interaction contract; agents are taught to test
18
+ # through them (the usage skill), so generated tests are correct by
19
+ # construction.
20
+ #
21
+ # require "poetry/ui/testing"
22
+ #
23
+ # class PlanTest < ApplicationSystemTestCase
24
+ # include Poetry::Ui::Testing
25
+ #
26
+ # test "picking a plan" do
27
+ # visit settings_path
28
+ # select = poetry_select("#plan")
29
+ # select.select_option("Pro", via: :keyboard)
30
+ # assert_equal "pro", select.value
31
+ # end
32
+ # end
33
+ #
34
+ # Testers locate parts by data-slot from the root you hand them, and
35
+ # wait with Capybara's own retry discipline - no sleeps. `via:` swaps
36
+ # the ENTIRE event sequence (:mouse clicks, :keyboard focuses the
37
+ # trigger and drives keys), because the two paths exercise different
38
+ # controller seams.
39
+ module Testing
40
+ # Each entry point takes the component ROOT: a CSS selector string
41
+ # or a Capybara node, and an optional session (defaults to
42
+ # Capybara.current_session, or the including test's `page`).
43
+
44
+ # A Select tester rooted at the component.
45
+ #
46
+ # @param root [String, Capybara::Node::Element] the component root
47
+ # @param session [Capybara::Session, nil] defaults to the test's
48
+ # `page` (or Capybara.current_session)
49
+ # @return [Select]
50
+ def poetry_select(root, session: nil)
51
+ Select.new(root, session: session || testing_session)
52
+ end
53
+
54
+ # A Combobox tester rooted at the component (single or multiple).
55
+ #
56
+ # @param root [String, Capybara::Node::Element] the component root
57
+ # @param session [Capybara::Session, nil] defaults to the test's
58
+ # `page` (or Capybara.current_session)
59
+ # @return [Combobox]
60
+ def poetry_combobox(root, session: nil)
61
+ Combobox.new(root, session: session || testing_session)
62
+ end
63
+
64
+ # A DropdownMenu tester rooted at the component.
65
+ #
66
+ # @param root [String, Capybara::Node::Element] the component root
67
+ # @param session [Capybara::Session, nil] defaults to the test's
68
+ # `page` (or Capybara.current_session)
69
+ # @return [Menu]
70
+ def poetry_dropdown_menu(root, session: nil)
71
+ Menu.new(root, session: session || testing_session)
72
+ end
73
+
74
+ # A Dialog tester rooted at the component.
75
+ #
76
+ # @param root [String, Capybara::Node::Element] the element carrying
77
+ # data-component=dialog (trigger + content)
78
+ # @param session [Capybara::Session, nil] defaults to the test's
79
+ # `page` (or Capybara.current_session)
80
+ # @return [Dialog]
81
+ def poetry_dialog(root, session: nil)
82
+ Dialog.new(root, session: session || testing_session)
83
+ end
84
+
85
+ # Every poetry controller identifier on the current page that the
86
+ # host's Stimulus application has NOT registered - empty when the
87
+ # wiring is healthy. Stimulus never errors on an unknown identifier
88
+ # (the element just stays inert), and one failed import in the
89
+ # controllers graph silently takes every poetry controller with it,
90
+ # so nothing else surfaces this.
91
+ #
92
+ # @param session [Capybara::Session, nil] defaults to the test's
93
+ # `page` (or Capybara.current_session)
94
+ # @param application [String] JS expression naming the Stimulus
95
+ # application (Rails' default controllers/application.js exposes
96
+ # window.Stimulus)
97
+ # @return [Array<String>] the unregistered identifiers, sorted
98
+ # @raise [RegistrationError] when no application is reachable at all
99
+ def poetry_unregistered_controllers(session: nil, application: "window.Stimulus")
100
+ result = (session || testing_session).evaluate_script(Registration.script(application))
101
+ raise RegistrationError, result["error"] if result["error"]
102
+
103
+ result["missing"]
104
+ end
105
+
106
+ # Asserts that every poetry controller on the page is registered.
107
+ # Flunks under Minitest, raises {RegistrationError} elsewhere, with
108
+ # the identifiers and the two causes to check first.
109
+ #
110
+ # @param session [Capybara::Session, nil] defaults to the test's
111
+ # `page` (or Capybara.current_session)
112
+ # @param application [String] JS expression naming the Stimulus
113
+ # application (default window.Stimulus)
114
+ # @return [true]
115
+ def assert_poetry_controllers_registered(session: nil, application: "window.Stimulus")
116
+ result = (session || testing_session).evaluate_script(Registration.script(application))
117
+ return true if !result["error"] && result["missing"].empty?
118
+
119
+ message = Registration.message(result)
120
+ respond_to?(:flunk, true) ? flunk(message) : raise(RegistrationError, message)
121
+ end
122
+
123
+ private
124
+
125
+ def testing_session
126
+ respond_to?(:page) ? page : Capybara.current_session
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The theme roster + per-theme DESIGN.md metadata.
6
+ #
7
+ # Every poetry theme shares ONE token source (poetry-core's DTCG file) -
8
+ # a theme is a component-treatment layer (themes/<name>.css), never a
9
+ # palette. What varies per theme, and therefore what this module knows:
10
+ # the treatment provenance line and the typography PAIRING - which is
11
+ # app-level metadata, not CSS (no poetry theme moves a font
12
+ # token; upstream's create flow biases lyra to JetBrains Mono with
13
+ # radius none and pairs sera with Noto Serif / Instrument Serif; the
14
+ # poetry docs render system stacks keyed off the same story).
15
+ module Themes
16
+ # The system sans stack sans-paired themes report.
17
+ SANS = "ui-sans-serif, system-ui, sans-serif"
18
+ # The system mono stack mono-biased themes report.
19
+ MONO = %(ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace)
20
+ # The system serif stack serif-paired themes report.
21
+ SERIF = %(ui-serif, Georgia, Cambria, "Times New Roman", serif)
22
+
23
+ # The provenance line template for ported treatments,
24
+ # format-interpolated with the upstream style name.
25
+ PORTED = "ported from the source style-%s.css at pin shadcn@4.19.0 (1773ecfe)"
26
+
27
+ # Per-theme DESIGN.md metadata: the typography pairing and the
28
+ # treatment provenance line.
29
+ DETAILS = {
30
+ "default" => {
31
+ "typography" => { "pairing" => "system sans", "family" => SANS },
32
+ "treatment" => "the neutral source-parity treatment (the ported baseline)"
33
+ },
34
+ "vega" => {
35
+ "typography" => { "pairing" => "system sans", "family" => SANS },
36
+ "treatment" => "the vega treatment, #{format(PORTED, "vega")}"
37
+ },
38
+ "nova" => {
39
+ "typography" => { "pairing" => "system sans", "family" => SANS },
40
+ "treatment" => "the nova treatment, #{format(PORTED, "nova")}"
41
+ },
42
+ "mira" => {
43
+ "typography" => { "pairing" => "system sans", "family" => SANS },
44
+ "treatment" => "the mira treatment, #{format(PORTED, "mira")}"
45
+ },
46
+ "rhea" => {
47
+ "typography" => { "pairing" => "system sans", "family" => SANS },
48
+ "treatment" => "the rhea treatment (tinted surfaces), #{format(PORTED, "rhea")}"
49
+ },
50
+ "maia" => {
51
+ "typography" => { "pairing" => "system sans", "family" => SANS },
52
+ "treatment" => "the maia treatment, #{format(PORTED, "maia")}"
53
+ },
54
+ "luma" => {
55
+ "typography" => { "pairing" => "system sans", "family" => SANS },
56
+ "treatment" => "the luma treatment, #{format(PORTED, "luma")}"
57
+ },
58
+ "lyra" => {
59
+ "typography" => { "pairing" => "system mono (upstream: JetBrains Mono, radius none)", "family" => MONO },
60
+ "treatment" => "the lyra treatment (flat, mono-biased), #{format(PORTED, "lyra")}"
61
+ },
62
+ "sera" => {
63
+ "typography" => { "pairing" => "system serif (upstream: Noto Serif / Instrument Serif)",
64
+ "family" => SERIF },
65
+ "treatment" => "the sera treatment (serif-paired), #{format(PORTED, "sera")}"
66
+ }
67
+ }.freeze
68
+
69
+ # A host whose installed theme slot matches no shipped fragment
70
+ # (hand-customized bytes) still exports honestly.
71
+ CUSTOM_DETAILS = {
72
+ "typography" => { "pairing" => "system sans", "family" => SANS },
73
+ "treatment" => "a customized component treatment (no byte match with a shipped poetry theme)"
74
+ }.freeze
75
+
76
+ class << self
77
+ # The shipped roster, from the fragment files themselves.
78
+ #
79
+ # @return [Array<String>] theme names, sorted
80
+ def names
81
+ Dir[Poetry::Ui.root.join("themes/*.css").to_s].map { |file| File.basename(file, ".css") }.sort
82
+ end
83
+
84
+ # The DESIGN.md metadata for one theme; unknown names (customized
85
+ # host bytes) fall back to CUSTOM_DETAILS.
86
+ #
87
+ # @param name [String] the theme name
88
+ # @return [Hash] "typography" pairing + "treatment" provenance
89
+ def details(name)
90
+ DETAILS.fetch(name) { CUSTOM_DETAILS }
91
+ end
92
+
93
+ # theme name -> serialized DESIGN.md, for every shipped theme - the
94
+ # single builder rake design:export_all, the drift gate, and the
95
+ # tests all share.
96
+ def design_md_exports(components_count:, generator: "bin/rake design:export_all")
97
+ tokens = Poetry::Core::Tokens.load
98
+ names.to_h do |name|
99
+ doc = Poetry::Core::DesignMd.build(
100
+ tokens: tokens, theme: name,
101
+ details: details(name).merge("components_count" => components_count, "generator" => generator)
102
+ )
103
+ [name, Poetry::Core::DesignMd.serialize(doc)]
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The gem version.
6
+ VERSION = "0.0.2"
7
+ end
8
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # The declarative WebMCP form contract: the attribute vocabulary
6
+ # Chrome's declarative API registers straight from markup - a <form>
7
+ # carrying `toolname` + `tooldescription` IS an agent-callable tool,
8
+ # its controls are the parameters (the browser synthesizes the JSON
9
+ # Schema from control types, `required`, and select options, reading
10
+ # each parameter's description from the associated <label>), and
11
+ # `toolparamdescription` overrides a label where the label alone is
12
+ # not agent-sufficient. No JavaScript, inert markup without an agent.
13
+ #
14
+ # Safety by construction: `toolautosubmit` (the agent submits without
15
+ # the user pressing Submit) is only allowed on GET forms - read-only
16
+ # lookups. A mutating form keeps the user in the loop, always.
17
+ #
18
+ # Consumers: {ComponentsHelper#poetry_webmcp_form} (the form), the
19
+ # FormBuilder's `tool_description:` field option (the override), and
20
+ # poetry-agent's imperative runtime, which shares this vocabulary.
21
+ module Webmcp
22
+ # The WebMCP tool-name grammar: 1-128 ASCII alphanumerics, `_`,
23
+ # `-`, `.` (declarative forms register their full name directly).
24
+ TOOL_NAME_PATTERN = /\A[A-Za-z0-9_.-]{1,128}\z/
25
+ # The agent-legibility budget for descriptions.
26
+ DESCRIPTION_LIMIT = 500
27
+ # The keys a `tool:` declaration may carry.
28
+ TOOL_KEYS = %i[name description autosubmit].freeze
29
+ # poetry-agent's declarative-form controller (the respondWith path);
30
+ # emitted as a plain token so the markup stays inert without the gem.
31
+ FORM_CONTROLLER = "poetry--agent--webmcp-form"
32
+
33
+ module_function
34
+
35
+ # The <form> attributes for a declarative tool.
36
+ #
37
+ # @param tool [Hash] `name:` (tool-name grammar), `description:`
38
+ # (present, at most {DESCRIPTION_LIMIT} chars), optional
39
+ # `autosubmit: true`
40
+ # @param method [Symbol, String, nil] the form's HTTP method -
41
+ # autosubmit requires :get
42
+ # @return [Hash{Symbol => String}] `toolname`, `tooldescription`,
43
+ # and `toolautosubmit` (empty-valued boolean attribute) when set
44
+ # @raise [ArgumentError] for an unknown key, an invalid name or
45
+ # description, or autosubmit on a non-GET form
46
+ # @example
47
+ # Poetry::Ui::Webmcp.form_attributes({ name: "find_order", description: "Find an order." }, method: :get)
48
+ # # => { toolname: "find_order", tooldescription: "Find an order." }
49
+ def form_attributes(tool, method: nil)
50
+ tool = tool.to_h.transform_keys(&:to_sym)
51
+ unknown = tool.keys - TOOL_KEYS
52
+ raise ArgumentError, "tool: unknown key(s) #{unknown.inspect} - allowed: #{TOOL_KEYS.inspect}" if unknown.any?
53
+
54
+ name = tool[:name].to_s
55
+ unless TOOL_NAME_PATTERN.match?(name)
56
+ raise ArgumentError, "tool: name #{tool[:name].inspect} must be 1-128 chars of [A-Za-z0-9_.-]"
57
+ end
58
+
59
+ attributes = { toolname: name, tooldescription: description!(tool[:description], "tool: description") }
60
+ if tool[:autosubmit]
61
+ unless method.to_s.casecmp?("get")
62
+ raise ArgumentError,
63
+ "tool: autosubmit is only allowed on GET forms (read-only lookups) - a mutating form " \
64
+ "keeps the user's confirmation"
65
+ end
66
+ attributes[:toolautosubmit] = ""
67
+ end
68
+ attributes
69
+ end
70
+
71
+ # The control attribute for a parameter-description override.
72
+ #
73
+ # @param description [String] the parameter's description for agents
74
+ # @return [Hash{Symbol => String}] `toolparamdescription`
75
+ # @raise [ArgumentError] for a blank or over-budget description
76
+ def param_attributes(description)
77
+ { toolparamdescription: description!(description, "tool_description:") }
78
+ end
79
+
80
+ # @api private
81
+ def description!(value, context)
82
+ text = value.to_s.strip
83
+ if text.empty? || text.length > DESCRIPTION_LIMIT
84
+ raise ArgumentError, "#{context} must be present and at most #{DESCRIPTION_LIMIT} characters"
85
+ end
86
+
87
+ text
88
+ end
89
+ end
90
+ end
91
+ end