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.
- checksums.yaml +7 -0
- data/.gitattributes +6 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +87 -0
- data/THIRD_PARTY_NOTICES.md +201 -0
- data/app/components/poetry/ui/accordion/component.html.erb +3 -0
- data/app/components/poetry/ui/accordion/component.rb +197 -0
- data/app/components/poetry/ui/accordion/preview.rb +39 -0
- data/app/components/poetry/ui/accordion/style.rb +37 -0
- data/app/components/poetry/ui/alert/component.html.erb +6 -0
- data/app/components/poetry/ui/alert/component.rb +61 -0
- data/app/components/poetry/ui/alert/preview.rb +35 -0
- data/app/components/poetry/ui/alert/style.rb +24 -0
- data/app/components/poetry/ui/alert_dialog/component.html.erb +17 -0
- data/app/components/poetry/ui/alert_dialog/component.rb +208 -0
- data/app/components/poetry/ui/alert_dialog/preview.rb +48 -0
- data/app/components/poetry/ui/alert_dialog/style.rb +46 -0
- data/app/components/poetry/ui/aspect_ratio/component.rb +63 -0
- data/app/components/poetry/ui/aspect_ratio/preview.rb +29 -0
- data/app/components/poetry/ui/aspect_ratio/style.rb +13 -0
- data/app/components/poetry/ui/attachment/component.html.erb +13 -0
- data/app/components/poetry/ui/attachment/component.rb +139 -0
- data/app/components/poetry/ui/attachment/preview.rb +83 -0
- data/app/components/poetry/ui/attachment/style.rb +59 -0
- data/app/components/poetry/ui/autocomplete/component.html.erb +16 -0
- data/app/components/poetry/ui/autocomplete/component.rb +193 -0
- data/app/components/poetry/ui/autocomplete/preview.rb +39 -0
- data/app/components/poetry/ui/autocomplete/style.rb +18 -0
- data/app/components/poetry/ui/avatar/component.rb +103 -0
- data/app/components/poetry/ui/avatar/group_preview.html.erb +11 -0
- data/app/components/poetry/ui/avatar/preview.rb +50 -0
- data/app/components/poetry/ui/avatar/style.rb +34 -0
- data/app/components/poetry/ui/badge/component.rb +73 -0
- data/app/components/poetry/ui/badge/preview.rb +54 -0
- data/app/components/poetry/ui/badge/style.rb +35 -0
- data/app/components/poetry/ui/breadcrumb/component.html.erb +32 -0
- data/app/components/poetry/ui/breadcrumb/component.rb +102 -0
- data/app/components/poetry/ui/breadcrumb/dropdown_crumb_preview.html.erb +21 -0
- data/app/components/poetry/ui/breadcrumb/preview.rb +45 -0
- data/app/components/poetry/ui/breadcrumb/style.rb +25 -0
- data/app/components/poetry/ui/bubble/component.html.erb +4 -0
- data/app/components/poetry/ui/bubble/component.rb +106 -0
- data/app/components/poetry/ui/bubble/preview.rb +62 -0
- data/app/components/poetry/ui/bubble/style.rb +47 -0
- data/app/components/poetry/ui/button/component.html.erb +14 -0
- data/app/components/poetry/ui/button/component.rb +168 -0
- data/app/components/poetry/ui/button/preview.rb +94 -0
- data/app/components/poetry/ui/button/style.rb +41 -0
- data/app/components/poetry/ui/button_group/component.rb +70 -0
- data/app/components/poetry/ui/button_group/popup_triggers_preview.html.erb +21 -0
- data/app/components/poetry/ui/button_group/preview.rb +46 -0
- data/app/components/poetry/ui/button_group/style.rb +27 -0
- data/app/components/poetry/ui/button_group/text_prefix_preview.html.erb +7 -0
- data/app/components/poetry/ui/calendar/component.html.erb +72 -0
- data/app/components/poetry/ui/calendar/component.rb +366 -0
- data/app/components/poetry/ui/calendar/preview.rb +55 -0
- data/app/components/poetry/ui/calendar/style.rb +55 -0
- data/app/components/poetry/ui/card/component.html.erb +11 -0
- data/app/components/poetry/ui/card/component.rb +90 -0
- data/app/components/poetry/ui/card/preview.rb +29 -0
- data/app/components/poetry/ui/card/style.rb +23 -0
- data/app/components/poetry/ui/carousel/component.html.erb +17 -0
- data/app/components/poetry/ui/carousel/component.rb +179 -0
- data/app/components/poetry/ui/carousel/preview.rb +67 -0
- data/app/components/poetry/ui/carousel/style.rb +39 -0
- data/app/components/poetry/ui/checkbox/component.html.erb +22 -0
- data/app/components/poetry/ui/checkbox/component.rb +179 -0
- data/app/components/poetry/ui/checkbox/preview.rb +68 -0
- data/app/components/poetry/ui/checkbox/style.rb +28 -0
- data/app/components/poetry/ui/clipboard_text/component.html.erb +15 -0
- data/app/components/poetry/ui/clipboard_text/component.rb +154 -0
- data/app/components/poetry/ui/clipboard_text/preview.rb +24 -0
- data/app/components/poetry/ui/clipboard_text/style.rb +23 -0
- data/app/components/poetry/ui/code_block/component.html.erb +13 -0
- data/app/components/poetry/ui/code_block/component.rb +141 -0
- data/app/components/poetry/ui/code_block/preview.rb +40 -0
- data/app/components/poetry/ui/code_block/style.rb +38 -0
- data/app/components/poetry/ui/collapsible/component.html.erb +4 -0
- data/app/components/poetry/ui/collapsible/component.rb +134 -0
- data/app/components/poetry/ui/collapsible/preview.rb +24 -0
- data/app/components/poetry/ui/combobox/component.html.erb +49 -0
- data/app/components/poetry/ui/combobox/component.rb +1086 -0
- data/app/components/poetry/ui/combobox/preview.rb +164 -0
- data/app/components/poetry/ui/combobox/style.rb +148 -0
- data/app/components/poetry/ui/command/component.html.erb +20 -0
- data/app/components/poetry/ui/command/component.rb +526 -0
- data/app/components/poetry/ui/command/dialog_component.html.erb +14 -0
- data/app/components/poetry/ui/command/dialog_component.rb +206 -0
- data/app/components/poetry/ui/command/dialog_preview.rb +39 -0
- data/app/components/poetry/ui/command/preview.rb +121 -0
- data/app/components/poetry/ui/command/style.rb +78 -0
- data/app/components/poetry/ui/composable_trigger.rb +63 -0
- data/app/components/poetry/ui/context_menu/component.html.erb +6 -0
- data/app/components/poetry/ui/context_menu/component.rb +358 -0
- data/app/components/poetry/ui/context_menu/preview.rb +111 -0
- data/app/components/poetry/ui/context_menu/style.rb +59 -0
- data/app/components/poetry/ui/data_table/component.html.erb +78 -0
- data/app/components/poetry/ui/data_table/component.rb +277 -0
- data/app/components/poetry/ui/data_table/preview.rb +68 -0
- data/app/components/poetry/ui/data_table/state.rb +115 -0
- data/app/components/poetry/ui/data_table/style.rb +42 -0
- data/app/components/poetry/ui/date_field/component.html.erb +7 -0
- data/app/components/poetry/ui/date_field/component.rb +209 -0
- data/app/components/poetry/ui/date_field/preview.rb +42 -0
- data/app/components/poetry/ui/date_field/style.rb +35 -0
- data/app/components/poetry/ui/date_picker/component.html.erb +23 -0
- data/app/components/poetry/ui/date_picker/component.rb +242 -0
- data/app/components/poetry/ui/date_picker/preview.rb +31 -0
- data/app/components/poetry/ui/date_picker/style.rb +25 -0
- data/app/components/poetry/ui/deferred/component.html.erb +16 -0
- data/app/components/poetry/ui/deferred/component.rb +90 -0
- data/app/components/poetry/ui/deferred/preview.rb +24 -0
- data/app/components/poetry/ui/dialog/component.html.erb +21 -0
- data/app/components/poetry/ui/dialog/component.rb +194 -0
- data/app/components/poetry/ui/dialog/preview.rb +33 -0
- data/app/components/poetry/ui/dialog/style.rb +30 -0
- data/app/components/poetry/ui/drawer/component.html.erb +16 -0
- data/app/components/poetry/ui/drawer/component.rb +207 -0
- data/app/components/poetry/ui/drawer/preview.rb +69 -0
- data/app/components/poetry/ui/drawer/style.rb +105 -0
- data/app/components/poetry/ui/dropdown_menu/component.html.erb +6 -0
- data/app/components/poetry/ui/dropdown_menu/component.rb +350 -0
- data/app/components/poetry/ui/dropdown_menu/preview.rb +101 -0
- data/app/components/poetry/ui/dropdown_menu/style.rb +60 -0
- data/app/components/poetry/ui/empty/component.html.erb +10 -0
- data/app/components/poetry/ui/empty/component.rb +79 -0
- data/app/components/poetry/ui/empty/preview.rb +27 -0
- data/app/components/poetry/ui/empty/style.rb +27 -0
- data/app/components/poetry/ui/family_identity.rb +52 -0
- data/app/components/poetry/ui/field/component.html.erb +19 -0
- data/app/components/poetry/ui/field/component.rb +183 -0
- data/app/components/poetry/ui/field/preview.html.erb +30 -0
- data/app/components/poetry/ui/field/preview.rb +72 -0
- data/app/components/poetry/ui/field/style.rb +68 -0
- data/app/components/poetry/ui/field_group/component.rb +61 -0
- data/app/components/poetry/ui/field_group/preview.html.erb +30 -0
- data/app/components/poetry/ui/field_group/preview.rb +20 -0
- data/app/components/poetry/ui/field_group/style.rb +25 -0
- data/app/components/poetry/ui/field_separator/component.rb +70 -0
- data/app/components/poetry/ui/field_separator/preview.rb +18 -0
- data/app/components/poetry/ui/field_separator/style.rb +20 -0
- data/app/components/poetry/ui/fieldset/component.rb +90 -0
- data/app/components/poetry/ui/fieldset/preview.html.erb +22 -0
- data/app/components/poetry/ui/fieldset/preview.rb +25 -0
- data/app/components/poetry/ui/fieldset/style.rb +18 -0
- data/app/components/poetry/ui/file_input/component.html.erb +14 -0
- data/app/components/poetry/ui/file_input/component.rb +188 -0
- data/app/components/poetry/ui/file_input/preview.rb +30 -0
- data/app/components/poetry/ui/file_input/style.rb +35 -0
- data/app/components/poetry/ui/hover_card/component.html.erb +6 -0
- data/app/components/poetry/ui/hover_card/component.rb +201 -0
- data/app/components/poetry/ui/hover_card/preview.rb +60 -0
- data/app/components/poetry/ui/hover_card/style.rb +17 -0
- data/app/components/poetry/ui/icon/component.rb +127 -0
- data/app/components/poetry/ui/icon/preview.rb +21 -0
- data/app/components/poetry/ui/input/component.rb +80 -0
- data/app/components/poetry/ui/input/preview.rb +35 -0
- data/app/components/poetry/ui/input/style.rb +18 -0
- data/app/components/poetry/ui/input_group/button_preview.html.erb +12 -0
- data/app/components/poetry/ui/input_group/component.rb +69 -0
- data/app/components/poetry/ui/input_group/preview.rb +23 -0
- data/app/components/poetry/ui/input_group/search_preview.html.erb +13 -0
- data/app/components/poetry/ui/input_group/style.rb +40 -0
- data/app/components/poetry/ui/input_group/textarea_preview.html.erb +10 -0
- data/app/components/poetry/ui/input_group_field.rb +61 -0
- data/app/components/poetry/ui/input_otp/component.html.erb +25 -0
- data/app/components/poetry/ui/input_otp/component.rb +250 -0
- data/app/components/poetry/ui/input_otp/preview.rb +91 -0
- data/app/components/poetry/ui/input_otp/style.rb +36 -0
- data/app/components/poetry/ui/item/component.html.erb +13 -0
- data/app/components/poetry/ui/item/component.rb +99 -0
- data/app/components/poetry/ui/item/group_preview.html.erb +15 -0
- data/app/components/poetry/ui/item/preview.rb +52 -0
- data/app/components/poetry/ui/item/style.rb +45 -0
- data/app/components/poetry/ui/kbd/component.rb +47 -0
- data/app/components/poetry/ui/kbd/group_preview.html.erb +4 -0
- data/app/components/poetry/ui/kbd/preview.rb +23 -0
- data/app/components/poetry/ui/kbd/style.rb +13 -0
- data/app/components/poetry/ui/label/component.rb +46 -0
- data/app/components/poetry/ui/label/preview.html.erb +5 -0
- data/app/components/poetry/ui/label/preview.rb +16 -0
- data/app/components/poetry/ui/label/style.rb +13 -0
- data/app/components/poetry/ui/link/component.rb +62 -0
- data/app/components/poetry/ui/link/preview.rb +31 -0
- data/app/components/poetry/ui/link/style.rb +20 -0
- data/app/components/poetry/ui/marker/component.html.erb +4 -0
- data/app/components/poetry/ui/marker/component.rb +81 -0
- data/app/components/poetry/ui/marker/preview.rb +43 -0
- data/app/components/poetry/ui/marker/style.rb +24 -0
- data/app/components/poetry/ui/menubar/component.html.erb +3 -0
- data/app/components/poetry/ui/menubar/component.rb +493 -0
- data/app/components/poetry/ui/menubar/preview.rb +164 -0
- data/app/components/poetry/ui/menubar/style.rb +71 -0
- data/app/components/poetry/ui/menus.rb +386 -0
- data/app/components/poetry/ui/message/component.html.erb +8 -0
- data/app/components/poetry/ui/message/component.rb +64 -0
- data/app/components/poetry/ui/message/preview.rb +33 -0
- data/app/components/poetry/ui/message/style.rb +29 -0
- data/app/components/poetry/ui/message_scroller/component.html.erb +15 -0
- data/app/components/poetry/ui/message_scroller/component.rb +163 -0
- data/app/components/poetry/ui/message_scroller/preview.rb +33 -0
- data/app/components/poetry/ui/message_scroller/style.rb +39 -0
- data/app/components/poetry/ui/metadata_list/component.rb +88 -0
- data/app/components/poetry/ui/metadata_list/preview.rb +55 -0
- data/app/components/poetry/ui/metadata_list/style.rb +35 -0
- data/app/components/poetry/ui/meter/component.rb +117 -0
- data/app/components/poetry/ui/meter/preview.rb +28 -0
- data/app/components/poetry/ui/meter/style.rb +16 -0
- data/app/components/poetry/ui/native_select/component.rb +124 -0
- data/app/components/poetry/ui/native_select/labelled_preview.html.erb +7 -0
- data/app/components/poetry/ui/native_select/preview.rb +20 -0
- data/app/components/poetry/ui/native_select/style.rb +22 -0
- data/app/components/poetry/ui/navigation_menu/bar_preview.html.erb +14 -0
- data/app/components/poetry/ui/navigation_menu/component.html.erb +25 -0
- data/app/components/poetry/ui/navigation_menu/component.rb +254 -0
- data/app/components/poetry/ui/navigation_menu/preview.rb +20 -0
- data/app/components/poetry/ui/navigation_menu/style.rb +73 -0
- data/app/components/poetry/ui/navigation_menu/viewport_preview.html.erb +23 -0
- data/app/components/poetry/ui/number_field/component.html.erb +19 -0
- data/app/components/poetry/ui/number_field/component.rb +263 -0
- data/app/components/poetry/ui/number_field/preview.rb +66 -0
- data/app/components/poetry/ui/number_field/style.rb +14 -0
- data/app/components/poetry/ui/pagination/component.html.erb +40 -0
- data/app/components/poetry/ui/pagination/component.rb +182 -0
- data/app/components/poetry/ui/pagination/preview.rb +47 -0
- data/app/components/poetry/ui/pagination/style.rb +27 -0
- data/app/components/poetry/ui/popover/component.html.erb +17 -0
- data/app/components/poetry/ui/popover/component.rb +203 -0
- data/app/components/poetry/ui/popover/preview.html.erb +29 -0
- data/app/components/poetry/ui/popover/preview.rb +66 -0
- data/app/components/poetry/ui/popover/style.rb +26 -0
- data/app/components/poetry/ui/popper_consumer.rb +69 -0
- data/app/components/poetry/ui/progress/component.rb +95 -0
- data/app/components/poetry/ui/progress/preview.rb +22 -0
- data/app/components/poetry/ui/progress/style.rb +19 -0
- data/app/components/poetry/ui/questionnaire/component.html.erb +80 -0
- data/app/components/poetry/ui/questionnaire/component.rb +396 -0
- data/app/components/poetry/ui/questionnaire/preview.rb +99 -0
- data/app/components/poetry/ui/questionnaire/style.rb +53 -0
- data/app/components/poetry/ui/radio_group/component.html.erb +4 -0
- data/app/components/poetry/ui/radio_group/component.rb +323 -0
- data/app/components/poetry/ui/radio_group/preview.rb +109 -0
- data/app/components/poetry/ui/radio_group/style.rb +53 -0
- data/app/components/poetry/ui/resizable/component.html.erb +10 -0
- data/app/components/poetry/ui/resizable/component.rb +178 -0
- data/app/components/poetry/ui/resizable/preview.rb +42 -0
- data/app/components/poetry/ui/resizable/style.rb +30 -0
- data/app/components/poetry/ui/scroll_area/component.rb +76 -0
- data/app/components/poetry/ui/scroll_area/preview.rb +35 -0
- data/app/components/poetry/ui/scroll_area/style.rb +19 -0
- data/app/components/poetry/ui/search_field/component.html.erb +15 -0
- data/app/components/poetry/ui/search_field/component.rb +137 -0
- data/app/components/poetry/ui/search_field/preview.rb +23 -0
- data/app/components/poetry/ui/search_field/style.rb +18 -0
- data/app/components/poetry/ui/select/component.html.erb +15 -0
- data/app/components/poetry/ui/select/component.rb +639 -0
- data/app/components/poetry/ui/select/preview.rb +113 -0
- data/app/components/poetry/ui/select/style.rb +58 -0
- data/app/components/poetry/ui/sensitive_input/component.html.erb +29 -0
- data/app/components/poetry/ui/sensitive_input/component.rb +274 -0
- data/app/components/poetry/ui/sensitive_input/preview.rb +40 -0
- data/app/components/poetry/ui/sensitive_input/style.rb +49 -0
- data/app/components/poetry/ui/separator/component.rb +57 -0
- data/app/components/poetry/ui/separator/preview.rb +19 -0
- data/app/components/poetry/ui/separator/style.rb +15 -0
- data/app/components/poetry/ui/sheet/component.html.erb +21 -0
- data/app/components/poetry/ui/sheet/component.rb +97 -0
- data/app/components/poetry/ui/sheet/preview.rb +57 -0
- data/app/components/poetry/ui/sheet/style.rb +47 -0
- data/app/components/poetry/ui/sidebar/component.html.erb +20 -0
- data/app/components/poetry/ui/sidebar/component.rb +249 -0
- data/app/components/poetry/ui/sidebar/preview.rb +21 -0
- data/app/components/poetry/ui/sidebar/shell_preview.html.erb +47 -0
- data/app/components/poetry/ui/sidebar/style.rb +139 -0
- data/app/components/poetry/ui/skeleton/component.rb +39 -0
- data/app/components/poetry/ui/skeleton/preview.rb +22 -0
- data/app/components/poetry/ui/skeleton/style.rb +13 -0
- data/app/components/poetry/ui/slider/component.html.erb +13 -0
- data/app/components/poetry/ui/slider/component.rb +360 -0
- data/app/components/poetry/ui/slider/preview.rb +73 -0
- data/app/components/poetry/ui/slider/style.rb +55 -0
- data/app/components/poetry/ui/spinner/component.rb +58 -0
- data/app/components/poetry/ui/spinner/preview.rb +22 -0
- data/app/components/poetry/ui/spinner/style.rb +12 -0
- data/app/components/poetry/ui/stat/component.html.erb +13 -0
- data/app/components/poetry/ui/stat/component.rb +102 -0
- data/app/components/poetry/ui/stat/preview.rb +47 -0
- data/app/components/poetry/ui/stat/style.rb +29 -0
- data/app/components/poetry/ui/switch/component.html.erb +15 -0
- data/app/components/poetry/ui/switch/component.rb +136 -0
- data/app/components/poetry/ui/switch/preview.rb +66 -0
- data/app/components/poetry/ui/switch/style.rb +22 -0
- data/app/components/poetry/ui/table/component.html.erb +3 -0
- data/app/components/poetry/ui/table/component.rb +93 -0
- data/app/components/poetry/ui/table/preview.rb +96 -0
- data/app/components/poetry/ui/table/style.rb +26 -0
- data/app/components/poetry/ui/tabs/component.html.erb +10 -0
- data/app/components/poetry/ui/tabs/component.rb +253 -0
- data/app/components/poetry/ui/tabs/preview.rb +43 -0
- data/app/components/poetry/ui/tabs/style.rb +49 -0
- data/app/components/poetry/ui/tag_group/component.html.erb +8 -0
- data/app/components/poetry/ui/tag_group/component.rb +203 -0
- data/app/components/poetry/ui/tag_group/preview.rb +43 -0
- data/app/components/poetry/ui/tag_group/style.rb +29 -0
- data/app/components/poetry/ui/textarea/component.rb +64 -0
- data/app/components/poetry/ui/textarea/preview.rb +75 -0
- data/app/components/poetry/ui/textarea/style.rb +18 -0
- data/app/components/poetry/ui/time_field/component.html.erb +6 -0
- data/app/components/poetry/ui/time_field/component.rb +85 -0
- data/app/components/poetry/ui/time_field/preview.rb +40 -0
- data/app/components/poetry/ui/time_field/style.rb +30 -0
- data/app/components/poetry/ui/timeline/component.rb +134 -0
- data/app/components/poetry/ui/timeline/preview.rb +61 -0
- data/app/components/poetry/ui/timeline/style.rb +54 -0
- data/app/components/poetry/ui/toast/component.html.erb +24 -0
- data/app/components/poetry/ui/toast/component.rb +178 -0
- data/app/components/poetry/ui/toast/preview.rb +69 -0
- data/app/components/poetry/ui/toast/style.rb +33 -0
- data/app/components/poetry/ui/toast_trigger/addressed_preview.html.erb +12 -0
- data/app/components/poetry/ui/toast_trigger/component.html.erb +3 -0
- data/app/components/poetry/ui/toast_trigger/component.rb +82 -0
- data/app/components/poetry/ui/toast_trigger/default_preview.html.erb +11 -0
- data/app/components/poetry/ui/toast_trigger/preview.rb +19 -0
- data/app/components/poetry/ui/toaster/component.html.erb +1 -0
- data/app/components/poetry/ui/toaster/component.rb +99 -0
- data/app/components/poetry/ui/toaster/positions_preview.html.erb +24 -0
- data/app/components/poetry/ui/toaster/preview.rb +46 -0
- data/app/components/poetry/ui/toaster/stamp_preview.html.erb +15 -0
- data/app/components/poetry/ui/toaster/style.rb +26 -0
- data/app/components/poetry/ui/toggle/component.rb +120 -0
- data/app/components/poetry/ui/toggle/preview.rb +79 -0
- data/app/components/poetry/ui/toggle/style.rb +33 -0
- data/app/components/poetry/ui/toggle_group/component.html.erb +4 -0
- data/app/components/poetry/ui/toggle_group/component.rb +252 -0
- data/app/components/poetry/ui/toggle_group/preview.rb +143 -0
- data/app/components/poetry/ui/toggle_group/style.rb +33 -0
- data/app/components/poetry/ui/toolbar/component.rb +125 -0
- data/app/components/poetry/ui/toolbar/preview.rb +41 -0
- data/app/components/poetry/ui/toolbar/style.rb +20 -0
- data/app/components/poetry/ui/tooltip/component.html.erb +20 -0
- data/app/components/poetry/ui/tooltip/component.rb +185 -0
- data/app/components/poetry/ui/tooltip/preview.rb +74 -0
- data/app/components/poetry/ui/tooltip/previews/provider_row.html.erb +25 -0
- data/app/components/poetry/ui/tooltip/style.rb +24 -0
- data/app/components/poetry/ui/tree/component.html.erb +32 -0
- data/app/components/poetry/ui/tree/component.rb +233 -0
- data/app/components/poetry/ui/tree/preview.rb +55 -0
- data/app/components/poetry/ui/tree/style.rb +29 -0
- data/app/components/poetry/ui/typeset/component.rb +73 -0
- data/app/components/poetry/ui/typeset/preview.rb +45 -0
- data/app/components/poetry/ui/typeset/style.rb +15 -0
- data/app/controllers/poetry/ui/llms_controller.rb +28 -0
- data/app/helpers/poetry/ui/components_helper.rb +1794 -0
- data/app/helpers/poetry/ui/form_builder/type_inference.rb +117 -0
- data/app/helpers/poetry/ui/form_builder.rb +916 -0
- data/app/helpers/poetry/ui/optimistic_form_builder.rb +55 -0
- data/app/helpers/poetry/ui/toast_stream_actions.rb +27 -0
- data/config/component_descriptions.yml +96 -0
- data/config/component_registry.yml +12257 -0
- data/config/locales/en.yml +93 -0
- data/config/routes.rb +8 -0
- data/config/template_classes.txt +113 -0
- data/lib/generators/poetry/add/add_generator.rb +218 -0
- data/lib/generators/poetry/agent_rules/agent_rules_generator.rb +91 -0
- data/lib/generators/poetry/agents/agents_generator.rb +24 -0
- data/lib/generators/poetry/agents_section.rb +148 -0
- data/lib/generators/poetry/block/block_generator.rb +67 -0
- data/lib/generators/poetry/block/templates/action_bar.html.erb +45 -0
- data/lib/generators/poetry/block/templates/app_shell.html.erb +139 -0
- data/lib/generators/poetry/block/templates/data_index.html.erb +85 -0
- data/lib/generators/poetry/block/templates/destructive_panel.html.erb +26 -0
- data/lib/generators/poetry/block/templates/page_header.html.erb +25 -0
- data/lib/generators/poetry/block/templates/section_card.html.erb +42 -0
- data/lib/generators/poetry/block/templates/stepper.html.erb +79 -0
- data/lib/generators/poetry/block/templates/top_nav.html.erb +44 -0
- data/lib/generators/poetry/diff/diff_generator.rb +135 -0
- data/lib/generators/poetry/editor/editor_generator.rb +344 -0
- data/lib/generators/poetry/editor/templates/poetry-check-hook +81 -0
- data/lib/generators/poetry/install/install_generator.rb +364 -0
- data/lib/generators/poetry/pagination/pagination_generator.rb +76 -0
- data/lib/generators/poetry/pagination/templates/kaminari_paginator.html.erb +29 -0
- data/lib/generators/poetry/pagination/templates/poetry_link_renderer.rb +34 -0
- data/lib/generators/poetry/pagination/templates/poetry_pagy_helper.rb +28 -0
- data/lib/generators/poetry/recipes/agent_embed/app/javascript/controllers/agent_demo_controller.js +131 -0
- data/lib/generators/poetry/recipes/screen_data_index/app/controllers/orders_controller.rb +15 -0
- data/lib/generators/poetry/recipes/screen_data_index/app/views/orders/index.html.erb +7 -0
- data/lib/generators/poetry/recipes/screen_data_index/test/system/orders_index_test.rb +16 -0
- data/lib/generators/poetry/recipes/screen_settings/app/controllers/settings_controller.rb +14 -0
- data/lib/generators/poetry/recipes/screen_settings/app/views/settings/show.html.erb +9 -0
- data/lib/generators/poetry/recipes/screen_settings/test/system/settings_show_test.rb +16 -0
- data/lib/generators/poetry/scaffold_templates/scaffold_templates_generator.rb +60 -0
- data/lib/generators/poetry/scaffold_templates/templates/_form.html.erb.tt +77 -0
- data/lib/generators/poetry/scaffold_templates/templates/controller.rb.tt +78 -0
- data/lib/generators/poetry/scaffold_templates/templates/edit.html.erb.tt +14 -0
- data/lib/generators/poetry/scaffold_templates/templates/index.html.erb.tt +24 -0
- data/lib/generators/poetry/scaffold_templates/templates/new.html.erb.tt +11 -0
- data/lib/generators/poetry/scaffold_templates/templates/partial.html.erb.tt +13 -0
- data/lib/generators/poetry/scaffold_templates/templates/show.html.erb.tt +18 -0
- data/lib/generators/poetry/skill/skill_generator.rb +34 -0
- data/lib/generators/poetry/skill/templates/poetry-component/SKILL.md +58 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/anatomy.md +152 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/checklist.md +67 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/documentation.md +137 -0
- data/lib/generators/poetry/skill/templates/poetry-design/SKILL.md +56 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/audit.md +123 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/compose.md +73 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/figma.md +68 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/paper.md +56 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/study.md +47 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/theme.md +70 -0
- data/lib/generators/poetry/skills_section.rb +58 -0
- data/lib/poetry/ui/chat.rb +283 -0
- data/lib/poetry/ui/code_block_highlighter.rb +60 -0
- data/lib/poetry/ui/engine.rb +55 -0
- data/lib/poetry/ui/recipes.rb +157 -0
- data/lib/poetry/ui/testing/combobox.rb +167 -0
- data/lib/poetry/ui/testing/dialog.rb +73 -0
- data/lib/poetry/ui/testing/menu.rb +127 -0
- data/lib/poetry/ui/testing/registration.rb +58 -0
- data/lib/poetry/ui/testing/select.rb +147 -0
- data/lib/poetry/ui/testing/tester.rb +130 -0
- data/lib/poetry/ui/testing.rb +130 -0
- data/lib/poetry/ui/themes.rb +109 -0
- data/lib/poetry/ui/version.rb +8 -0
- data/lib/poetry/ui/webmcp.rb +91 -0
- data/lib/poetry/ui.rb +285 -0
- data/lib/poetry-ui.rb +5 -0
- data/lib/tasks/poetry/check.rake +73 -0
- data/lib/tasks/poetry/design.rake +293 -0
- data/lib/tasks/poetry/verify.rake +36 -0
- data/themes/default.css +1793 -0
- data/themes/luma.css +1764 -0
- data/themes/lyra.css +1768 -0
- data/themes/maia.css +1768 -0
- data/themes/mira.css +1760 -0
- data/themes/nova.css +1755 -0
- data/themes/rhea.css +1771 -0
- data/themes/sera.css +1757 -0
- data/themes/vega.css +1775 -0
- data/typeset/typeset.css +499 -0
- 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,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
|