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
data/lib/poetry/ui.rb
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "poetry/core"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require_relative "ui/version"
|
|
6
|
+
require_relative "ui/themes"
|
|
7
|
+
require_relative "ui/code_block_highlighter"
|
|
8
|
+
require_relative "ui/recipes"
|
|
9
|
+
require_relative "ui/chat"
|
|
10
|
+
require_relative "ui/webmcp"
|
|
11
|
+
|
|
12
|
+
# The poetry namespace: poetry-ui shares it with poetry-core (the DSL)
|
|
13
|
+
# and the optional poetry-charts.
|
|
14
|
+
module Poetry
|
|
15
|
+
# The component library: ViewComponents tracking the full ported
|
|
16
|
+
# component vocabulary, built entirely on
|
|
17
|
+
# poetry-core's PUBLIC DSL - if a component here needs private core API,
|
|
18
|
+
# that is a core API gap, not a license to reach in.
|
|
19
|
+
module Ui
|
|
20
|
+
# The committed static-template-class list (herb-extracted in poetry's
|
|
21
|
+
# CI, drift-gated) - poetry:install reads it so hosts never need herb.
|
|
22
|
+
TEMPLATE_CLASSES_PATH = "config/template_classes.txt"
|
|
23
|
+
|
|
24
|
+
# Where the block templates live: the generator's source tree,
|
|
25
|
+
# scanned by the registry builder, the template-class extraction,
|
|
26
|
+
# the dummy's /blocks previews, and the MCP server's describe_block.
|
|
27
|
+
BLOCKS_DIR = "lib/generators/poetry/block/templates"
|
|
28
|
+
|
|
29
|
+
# Curated composition edges: which components a copy-in of X also needs
|
|
30
|
+
# locally. Single-sourced here - the add generator's recursive copy AND
|
|
31
|
+
# every registry item's registryDependencies read this one map.
|
|
32
|
+
# (Replaced by registry-carried anatomy when the contract's anatomy
|
|
33
|
+
# section lands.)
|
|
34
|
+
COMPONENT_DEPENDENCIES = {
|
|
35
|
+
"button" => %w[icon],
|
|
36
|
+
"dialog" => %w[button icon],
|
|
37
|
+
"sheet" => %w[dialog button icon], # subclasses Dialog::Component (shared dialog controller)
|
|
38
|
+
"alert_dialog" => %w[dialog button], # shares the dialog controller + posture
|
|
39
|
+
"field" => %w[label input],
|
|
40
|
+
"alert" => %w[icon],
|
|
41
|
+
"select" => %w[icon], # chevrons + check; Field is an optional pairing, not a hard edge
|
|
42
|
+
"number_field" => %w[input input_group button icon], # composes their chrome
|
|
43
|
+
"stat" => %w[icon], # the delta's trend arrow
|
|
44
|
+
"toolbar" => %w[button input separator], # the typed slots render them
|
|
45
|
+
"file_input" => %w[input icon], # input variant wears Input; the dropzone's upload glyph
|
|
46
|
+
"date_field" => %w[input], # the pre-enhancement native input wears Input's chrome
|
|
47
|
+
"time_field" => %w[date_field input], # DateField at hour granularity - one segment engine
|
|
48
|
+
"meter" => %w[progress], # wears Progress::Style chrome verbatim
|
|
49
|
+
"search_field" => %w[input input_group button icon], # composes their chrome
|
|
50
|
+
"tag_group" => %w[icon], # the remove glyph
|
|
51
|
+
"timeline" => %w[icon], # the indicator glyph
|
|
52
|
+
"tree" => %w[icon], # the chevron
|
|
53
|
+
"clipboard_text" => %w[input input_group button icon], # composes their chrome
|
|
54
|
+
"sensitive_input" => %w[input input_group button icon clipboard_text], # + the copy: engine
|
|
55
|
+
"code_block" => %w[button icon] # the copy affordance
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
# The usage skill's family partition: every component in exactly one
|
|
59
|
+
# reference file, so the skill's menu stays lean and an agent loads
|
|
60
|
+
# only the family it is composing in. The coverage gate fails on any
|
|
61
|
+
# new component until it is mapped here.
|
|
62
|
+
SKILL_FAMILIES = {
|
|
63
|
+
"forms" => %w[autocomplete button button_group calendar checkbox combobox date_field date_picker field
|
|
64
|
+
field_group field_separator fieldset
|
|
65
|
+
file_input input input_group input_otp label native_select number_field
|
|
66
|
+
questionnaire radio_group search_field select sensitive_input slider switch textarea time_field
|
|
67
|
+
toggle toggle_group],
|
|
68
|
+
"overlays" => %w[alert_dialog command command_dialog context_menu dialog drawer
|
|
69
|
+
dropdown_menu hover_card menubar popover sheet tooltip],
|
|
70
|
+
"data" => %w[accordion avatar badge card carousel clipboard_text code_block collapsible data_table empty
|
|
71
|
+
item metadata_list meter stat table tag_group timeline toolbar tree typeset],
|
|
72
|
+
"feedback" => %w[alert deferred progress skeleton spinner toast toast_trigger toaster],
|
|
73
|
+
"navigation" => %w[breadcrumb navigation_menu pagination sidebar tabs],
|
|
74
|
+
"foundations" => %w[icon kbd link marker separator],
|
|
75
|
+
"chat" => %w[attachment bubble message message_scroller],
|
|
76
|
+
"layout" => %w[aspect_ratio resizable scroll_area]
|
|
77
|
+
}.freeze
|
|
78
|
+
|
|
79
|
+
class << self
|
|
80
|
+
# Gem root (the directory containing lib/, app/, config/).
|
|
81
|
+
def root
|
|
82
|
+
@root ||= Pathname.new(File.expand_path("../..", __dir__))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [Array<String>] the committed template-static classes
|
|
86
|
+
def template_classes
|
|
87
|
+
root.join(TEMPLATE_CLASSES_PATH).read.lines
|
|
88
|
+
.map(&:strip).reject { |line| line.empty? || line.start_with?("#") }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The public poetry_* helper names, so poetry check / poetry-agent know
|
|
92
|
+
# the full set (group / provider helpers AND the define_method'd part
|
|
93
|
+
# helpers like poetry_table_cell) WITHOUT booting Rails. The module's
|
|
94
|
+
# method bodies reference component constants only at call time, so
|
|
95
|
+
# requiring the file standalone is safe.
|
|
96
|
+
def helper_names
|
|
97
|
+
require root.join("app/helpers/poetry/ui/components_helper.rb")
|
|
98
|
+
ComponentsHelper.public_instance_methods(false).grep(/\Apoetry_/).map(&:to_s)
|
|
99
|
+
rescue StandardError
|
|
100
|
+
# Fallback: the static-def helpers (misses define_method ones) if the
|
|
101
|
+
# module can't load standalone in some host.
|
|
102
|
+
root.join("app/helpers/poetry/ui/components_helper.rb").read.scan(/def (poetry_[a-z_]+)/).flatten.uniq
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# The registry builder this gem commits from (booted contexts only:
|
|
106
|
+
# rake registry:generate/verify and the sync test share it, so the
|
|
107
|
+
# drift gate always compares against the exact construction that
|
|
108
|
+
# generated the file - helpers AND blocks sections included).
|
|
109
|
+
def registry
|
|
110
|
+
components = Poetry::Core::Registry.new(source_root: root).components
|
|
111
|
+
component_paths = components.map(&:component_path)
|
|
112
|
+
Poetry::Core::Registry.new(
|
|
113
|
+
components: components, source_root: root,
|
|
114
|
+
helpers: registry_helpers(component_paths: component_paths),
|
|
115
|
+
blocks: registry_blocks(component_paths: component_paths),
|
|
116
|
+
helper_args: registry_helper_args,
|
|
117
|
+
descriptions: registry_descriptions,
|
|
118
|
+
form_builder: Poetry::Ui::FormBuilder.registry_section
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# The editorial per-component descriptions merged into the registry
|
|
123
|
+
# (component_path => one-liner, from config/component_descriptions.yml).
|
|
124
|
+
# Absent file -> nil, so a registry without it stays lint-identical, like
|
|
125
|
+
# every other optional section.
|
|
126
|
+
def registry_descriptions
|
|
127
|
+
path = root.join("config/component_descriptions.yml")
|
|
128
|
+
path.exist? ? YAML.safe_load_file(path) : nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The interop item projection - registry items in the ecosystem's
|
|
132
|
+
# shared JSON item schema, boot-free
|
|
133
|
+
# from the COMMITTED registry - the docs site serves /r/*.json from
|
|
134
|
+
# this, and the add generator matches gem-satisfied dependencies
|
|
135
|
+
# against its names.
|
|
136
|
+
def registry_items
|
|
137
|
+
Poetry::Core::RegistryItems.new(
|
|
138
|
+
registry: YAML.safe_load_file(root.join(Poetry::Core::Registry::RELATIVE_PATH)),
|
|
139
|
+
root: root, gem_name: "poetry-ui", gem_version: VERSION,
|
|
140
|
+
dependencies: COMPONENT_DEPENDENCIES
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# The recipes projection: skill bundles, scaffold template
|
|
145
|
+
# sets, and screen slices as registry items -
|
|
146
|
+
# served at /r/*.json beside components and blocks, installed by
|
|
147
|
+
# poetry:add or any client speaking the same item schema.
|
|
148
|
+
def recipe_items
|
|
149
|
+
Poetry::Core::RecipeItems.new(
|
|
150
|
+
recipes: Recipes.definitions, gem_name: "poetry-ui", gem_version: VERSION
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# The installable component-usage skill: a lean SKILL.md menu +
|
|
155
|
+
# per-family references, generated from the live
|
|
156
|
+
# registry - the seam the poetry:skill generator and the eval
|
|
157
|
+
# harness share (the agents_section_text pattern).
|
|
158
|
+
def skill_files
|
|
159
|
+
Poetry::Core::SkillText.new(
|
|
160
|
+
registry: registry, families: SKILL_FAMILIES, charts_registry: charts_registry
|
|
161
|
+
).files
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Tolerant on charts, like the AGENTS.md census: a host without the
|
|
165
|
+
# gem (or with a stubbed/partial one) just drops the charts reference.
|
|
166
|
+
def charts_registry
|
|
167
|
+
return nil unless defined?(Poetry::Charts::Engine)
|
|
168
|
+
|
|
169
|
+
Poetry::Charts.registry
|
|
170
|
+
rescue StandardError
|
|
171
|
+
nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# The MCP server's runtime skill map (get_skill): the SAME
|
|
175
|
+
# files `rails g poetry:skill` writes, for hosts that cannot write
|
|
176
|
+
# files. Boot-free by construction - the usage skill regenerates from
|
|
177
|
+
# the COMMITTED registries (never the booted builders above), the
|
|
178
|
+
# design skill reads its static templates - so the exe can serve both
|
|
179
|
+
# without Rails. Lazy: nothing generates until an agent asks.
|
|
180
|
+
def agent_skills
|
|
181
|
+
{
|
|
182
|
+
"poetry" => -> { runtime_skill_files },
|
|
183
|
+
"poetry-design" => lambda {
|
|
184
|
+
require "generators/poetry/skills_section"
|
|
185
|
+
Object.new.extend(Poetry::Generators::SkillsSection).design_skill_files
|
|
186
|
+
},
|
|
187
|
+
"poetry-component" => lambda {
|
|
188
|
+
require "generators/poetry/skills_section"
|
|
189
|
+
Object.new.extend(Poetry::Generators::SkillsSection).component_skill_files
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# The usage-skill files regenerated boot-free from the COMMITTED
|
|
195
|
+
# registries (never the booted builders above) - what the MCP server
|
|
196
|
+
# and the recipes projection serve without Rails.
|
|
197
|
+
#
|
|
198
|
+
# @api private
|
|
199
|
+
def runtime_skill_files
|
|
200
|
+
Poetry::Core::SkillText.new(
|
|
201
|
+
registry: Poetry::Core::Registry.committed(root),
|
|
202
|
+
families: SKILL_FAMILIES, charts_registry: committed_charts_registry
|
|
203
|
+
).files
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# The charts reference rides along whenever the charts gem is present
|
|
207
|
+
# (its committed registry, not its booted builder); absent, the skill
|
|
208
|
+
# simply drops it - the charts_registry tolerance, boot-free.
|
|
209
|
+
def committed_charts_registry
|
|
210
|
+
require "poetry/charts"
|
|
211
|
+
Poetry::Core::Registry.committed(Poetry::Charts.root)
|
|
212
|
+
rescue LoadError, StandardError
|
|
213
|
+
nil
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# The parameter kinds that count as positional in a signature.
|
|
217
|
+
POSITIONAL_PARAM_KINDS = %i[req opt].freeze
|
|
218
|
+
|
|
219
|
+
# The registry "helper_args" map: max positional arity for EVERY
|
|
220
|
+
# poetry_* helper, introspected from the real method signatures, so
|
|
221
|
+
# the linter can flag the crash class of `poetry_link "text", href:`
|
|
222
|
+
# against a kwargs-only helper. Rest-signatures are omitted - the
|
|
223
|
+
# linter enforces arity only where a key exists.
|
|
224
|
+
#
|
|
225
|
+
# @api private
|
|
226
|
+
def registry_helper_args
|
|
227
|
+
helper_names.sort.filter_map do |name|
|
|
228
|
+
params = ComponentsHelper.instance_method(name.to_sym).parameters
|
|
229
|
+
next if params.any? { |kind, _param| kind == :rest }
|
|
230
|
+
|
|
231
|
+
[name, params.count { |kind, _param| POSITIONAL_PARAM_KINDS.include?(kind) }]
|
|
232
|
+
end.to_h
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# The registry "helpers" section: every poetry_* helper that
|
|
236
|
+
# maps to no component - group/provider/item wrappers - each carrying
|
|
237
|
+
# its declared value contract (ComponentsHelper::HELPER_CONTRACTS) or
|
|
238
|
+
# {} for a plain wrapper.
|
|
239
|
+
def registry_helpers(component_paths:)
|
|
240
|
+
mapped = component_paths.map { |path| "poetry_#{path.delete_prefix("poetry/ui/").tr("/", "_")}" }
|
|
241
|
+
(helper_names - mapped).sort.to_h do |name|
|
|
242
|
+
[name, ComponentsHelper::HELPER_CONTRACTS.fetch(name, {})]
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# The registry "blocks" section: every block template's metadata,
|
|
247
|
+
# all source-derived - title/description/keywords from the
|
|
248
|
+
# mandatory poetry:block header (keywords power the MCP compose
|
|
249
|
+
# tool's brief routing), the composed component list from the
|
|
250
|
+
# template's own poetry_* calls (longest-prefix fold:
|
|
251
|
+
# sidebar_menu_button counts as sidebar), the gem-relative template
|
|
252
|
+
# path for boot-free source reads. No hand-authored catalog to drift.
|
|
253
|
+
def registry_blocks(component_paths:)
|
|
254
|
+
titles = component_paths.map { |path| path.delete_prefix("poetry/ui/").tr("/", "_") }
|
|
255
|
+
Dir.glob(root.join(BLOCKS_DIR, "*.html.erb").to_s).to_h do |file|
|
|
256
|
+
source = File.read(file)
|
|
257
|
+
header = source.match(
|
|
258
|
+
/\A<%#\s*poetry:block\s+title="([^"]*)"\s+description="([^"]*)"(?:\s+keywords="([^"]*)")?\s*%>/
|
|
259
|
+
)
|
|
260
|
+
raise Poetry::Core::Error, "#{file} is missing its poetry:block header" unless header
|
|
261
|
+
|
|
262
|
+
entry = { "title" => header[1], "description" => header[2],
|
|
263
|
+
"components" => block_components(source, titles: titles),
|
|
264
|
+
"template" => "#{BLOCKS_DIR}/#{File.basename(file)}" }
|
|
265
|
+
entry["keywords"] = header[3].split(",").map(&:strip) if header[3]
|
|
266
|
+
[File.basename(file, ".html.erb").tr("_", "-"), entry]
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# The distinct components a block composes, folded from its poetry_*
|
|
271
|
+
# helper calls: each call maps to the LONGEST component title that
|
|
272
|
+
# prefixes it (poetry_sidebar_menu_badge -> sidebar; poetry_table_head
|
|
273
|
+
# -> table); calls matching no component (pure wrapper helpers like
|
|
274
|
+
# poetry_input_group_addon fold through input_group) are dropped
|
|
275
|
+
# rather than guessed.
|
|
276
|
+
def block_components(source, titles:)
|
|
277
|
+
source.scan(/\bpoetry_([a-z0-9_]+)/).flatten.uniq.filter_map do |called|
|
|
278
|
+
titles.select { |title| called == title || called.start_with?("#{title}_") }.max_by(&:length)
|
|
279
|
+
end.uniq.sort
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
require_relative "ui/engine"
|
data/lib/poetry-ui.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Host-side poetry check: lint the app's ERB against the committed
|
|
4
|
+
# registry + controllers manifest.
|
|
5
|
+
# Loaded automatically by the engine (lib/tasks).
|
|
6
|
+
namespace :poetry do
|
|
7
|
+
desc "Lint app ERB + icon declarations in app Ruby against the poetry registry " \
|
|
8
|
+
"(glob arg overrides the default sweep; POETRY_CHECK_JSON=1 for JSON)"
|
|
9
|
+
task :check, [:glob] => :environment do |_task, args|
|
|
10
|
+
# The linter parses ERB with herb, which stays out of poetry's runtime
|
|
11
|
+
# dependencies on purpose (hosts never need it to RENDER) - same
|
|
12
|
+
# optional-parser posture as poetry:verify's template gate.
|
|
13
|
+
unless Poetry::Core::CSS::TemplateClasses.available?
|
|
14
|
+
abort "poetry:check: the herb gem is required to parse templates - " \
|
|
15
|
+
"add `gem \"herb\"` to your Gemfile (development group is enough)"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Default sweep: ERB through the template tier, app Ruby through the
|
|
19
|
+
# icon-declaration tier (the FLASH_ICONS pattern lives in .rb).
|
|
20
|
+
patterns = args[:glob] ? [args[:glob]] : ["app/{views,components}/**/*.html.erb", "app/**/*.rb"]
|
|
21
|
+
paths = patterns.flat_map { |pattern| Dir.glob(Rails.root.join(pattern).to_s) }.uniq
|
|
22
|
+
if paths.empty?
|
|
23
|
+
warn "poetry check: no files matched #{patterns.join(", ").inspect}"
|
|
24
|
+
exit 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
helpers = Poetry::Ui.helper_names
|
|
28
|
+
roots = [Poetry::Ui.root]
|
|
29
|
+
if defined?(Poetry::Charts::ComponentsHelper)
|
|
30
|
+
helpers += Poetry::Charts::ComponentsHelper.public_instance_methods(false)
|
|
31
|
+
.grep(/\Apoetry_/).map(&:to_s)
|
|
32
|
+
# The charts registry must join the catalog, not just the helper
|
|
33
|
+
# names - a name-valid pathless helper reads as a yielding wrapper
|
|
34
|
+
# (the chart yieldless-block false-positive class).
|
|
35
|
+
roots << Poetry::Charts.root
|
|
36
|
+
end
|
|
37
|
+
# The active set's names light up membership validation (the value
|
|
38
|
+
# contract) and the declaration tier; a host without a
|
|
39
|
+
# registered set still checks icon-name shape.
|
|
40
|
+
icon_names = begin
|
|
41
|
+
Poetry::Core::Icons.set.names
|
|
42
|
+
rescue Poetry::Core::Error
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
catalog = Poetry::Core::Check::Catalog.from_registries(roots, helpers: helpers, icon_names: icon_names)
|
|
46
|
+
findings = Poetry::Core::Check::Runner.new(catalog).run(paths)
|
|
47
|
+
|
|
48
|
+
# The taste tier: design-slop warnings join the mechanical
|
|
49
|
+
# findings on request - same vocabulary, same JSON/text output. The
|
|
50
|
+
# stock-theme nudge fires only for a FOREIGN DESIGN.md (a brand waiting
|
|
51
|
+
# to be applied) - poetry's own export IS the current state.
|
|
52
|
+
if ENV["POETRY_CHECK_DESIGN"] == "1"
|
|
53
|
+
# DesignLint herb-parses templates - the .rb paths in the sweep are
|
|
54
|
+
# the declaration tier's, not its.
|
|
55
|
+
findings += paths.grep(/\.erb\z/).flat_map { |path| Poetry::Core::DesignLint.lint(File.read(path), file: path) }
|
|
56
|
+
design_md = Rails.root.join("DESIGN.md")
|
|
57
|
+
foreign = design_md.exist? && Poetry::Core::DesignMd.parse(design_md.read)["theme"].nil?
|
|
58
|
+
findings += Poetry::Core::DesignLint.lint_dom(
|
|
59
|
+
doc: nil,
|
|
60
|
+
context: { design_md_present: foreign,
|
|
61
|
+
overrides_present: Rails.root.join("app/assets/tailwind/poetry/design-overrides.css").exist? }
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if ENV["POETRY_CHECK_JSON"] == "1"
|
|
66
|
+
puts Poetry::Core::Check.to_json(findings)
|
|
67
|
+
else
|
|
68
|
+
puts Poetry::Core::Check.to_text(findings)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
exit 1 if findings.any? { |finding| finding.severity == :error }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Host-side DESIGN.md export: serialize THIS app's poetry design as
|
|
4
|
+
# the design-skill ecosystem's shared artifact, so external design
|
|
5
|
+
# skills can read the system the app actually ships. Loaded
|
|
6
|
+
# automatically by the engine (lib/tasks).
|
|
7
|
+
namespace :poetry do
|
|
8
|
+
namespace :design do
|
|
9
|
+
desc "Export this app's poetry design to DESIGN.md " \
|
|
10
|
+
"(POETRY_DESIGN_PATH overrides the target; POETRY_DESIGN_FORCE=1 overwrites a non-poetry file)"
|
|
11
|
+
task export: :environment do
|
|
12
|
+
path = Pathname.new(ENV["POETRY_DESIGN_PATH"] || Rails.root.join("DESIGN.md"))
|
|
13
|
+
if path.exist? && Poetry::Core::DesignMd.parse(path.read)["theme"].nil? && ENV["POETRY_DESIGN_FORCE"] != "1"
|
|
14
|
+
abort "poetry:design:export: #{path} exists and was not written by poetry - " \
|
|
15
|
+
"set POETRY_DESIGN_PATH to export elsewhere, or POETRY_DESIGN_FORCE=1 to overwrite"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
theme = poetry_design_installed_theme
|
|
19
|
+
registry = YAML.safe_load_file(Poetry::Ui.root.join(Poetry::Core::Registry::RELATIVE_PATH))
|
|
20
|
+
details = Poetry::Ui::Themes.details(theme).merge(
|
|
21
|
+
"components_count" => registry.fetch("components").size,
|
|
22
|
+
"generator" => "bin/rails poetry:design:export"
|
|
23
|
+
)
|
|
24
|
+
doc = Poetry::Core::DesignMd.build(tokens: Poetry::Core::Tokens.load, theme: theme, details: details,
|
|
25
|
+
deviations: poetry_design_declared_overrides)
|
|
26
|
+
poetry_design_apply_overrides(doc)
|
|
27
|
+
path.write(Poetry::Core::DesignMd.serialize(doc))
|
|
28
|
+
|
|
29
|
+
poetry_design_warn_on_token_drift
|
|
30
|
+
puts "poetry:design:export: wrote #{path} (theme #{theme})"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The .cn-* override contract (the intent-vs-accident
|
|
34
|
+
# model): token-level restyling has a sanctioned channel
|
|
35
|
+
# (poetry:design:import -> design-overrides.css); host CSS that targets
|
|
36
|
+
# theme-owned .cn-* classes is the OTHER channel, and every such
|
|
37
|
+
# override must be declared - dated, reasoned, scoped - under
|
|
38
|
+
# `overrides:` in config/poetry_components.yml. This task reports both
|
|
39
|
+
# directions (undeclared drift AND stale declarations); undeclared
|
|
40
|
+
# findings print a paste-ready declaration. Never silence a finding to
|
|
41
|
+
# skip fixing it - declare only what the design owner confirms.
|
|
42
|
+
desc "Report host CSS overriding theme-owned .cn-* classes against the declared contract " \
|
|
43
|
+
"(config/poetry_components.yml `overrides:`; STRICT=1 exits nonzero on undeclared/invalid)"
|
|
44
|
+
task overrides: :environment do
|
|
45
|
+
scan = Poetry::Core::CSS::OverrideScan.new(
|
|
46
|
+
sources: poetry_design_host_css_sources,
|
|
47
|
+
declarations: poetry_design_declared_overrides
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
scan.invalid.each { |message| puts " INVALID #{message}" }
|
|
51
|
+
scan.stale.each do |declaration|
|
|
52
|
+
puts " STALE overrides[#{declaration.index}] (cn: #{declaration.cn.inspect}) matches no host CSS - " \
|
|
53
|
+
"remove it or fix its `files:` scope"
|
|
54
|
+
end
|
|
55
|
+
scan.undeclared.each do |path, classes|
|
|
56
|
+
puts " UNDECLARED #{path}: #{classes.join(", ")}"
|
|
57
|
+
puts " declare it (after confirming intent) in config/poetry_components.yml under overrides:"
|
|
58
|
+
puts scan.snippet_for(path, classes).gsub(/^/, " ")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if scan.ok?
|
|
62
|
+
puts "poetry:design:overrides: clean - #{scan.declared_count} declared override(s), " \
|
|
63
|
+
"#{scan.stale.size} stale declaration(s)"
|
|
64
|
+
else
|
|
65
|
+
message = "poetry:design:overrides: #{scan.undeclared.sum { |_, c| c.size }} undeclared .cn-* " \
|
|
66
|
+
"override(s), #{scan.invalid.size} invalid declaration(s)"
|
|
67
|
+
ENV["STRICT"] == "1" ? abort(message) : puts(message)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
desc "Import a design source into token overrides " \
|
|
72
|
+
"(a DESIGN.md, a Figma/DTCG variables .json, or a Paper/CSS .css theme -> " \
|
|
73
|
+
"app/assets/tailwind/poetry/design-overrides.css; AA enforced at the door, " \
|
|
74
|
+
"POETRY_DESIGN_FORCE=1 ships failing pairs, POETRY_DESIGN_JSON=1 for a JSON report)"
|
|
75
|
+
task :import, [:path] => :environment do |_task, args|
|
|
76
|
+
poetry_design_run_import(args[:path], task: "poetry:design:import")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Design-tool token bridges: thin, discoverable entry points onto the SAME
|
|
81
|
+
# importer. `TokenImport.load` dispatches by extension, so a Figma variables
|
|
82
|
+
# .json or a Paper "Copy theme" .css runs through the exact AA-gated pipeline
|
|
83
|
+
# poetry:design:import uses -- imported swatches inherit the contrast law, the
|
|
84
|
+
# drop-not-fabricate rule, and the dark-mode pins for free.
|
|
85
|
+
namespace :figma do
|
|
86
|
+
desc "Import a Figma variables export (DTCG .json) into token overrides " \
|
|
87
|
+
"(same AA-gated pipeline as poetry:design:import; POETRY_DESIGN_FORCE / POETRY_DESIGN_JSON honored)"
|
|
88
|
+
task :import, [:path] => :environment do |_task, args|
|
|
89
|
+
poetry_design_run_import(args[:path], task: "poetry:figma:import")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
namespace :paper do
|
|
94
|
+
desc "Import a Paper 'Copy theme' export (CSS .css) into token overrides " \
|
|
95
|
+
"(same AA-gated pipeline as poetry:design:import; POETRY_DESIGN_FORCE / POETRY_DESIGN_JSON honored)"
|
|
96
|
+
task :import, [:path] => :environment do |_task, args|
|
|
97
|
+
poetry_design_run_import(args[:path], task: "poetry:paper:import")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Shared importer for the DESIGN.md / Figma / Paper entry points: one file in,
|
|
103
|
+
# one AA-gated design-overrides.css out. Poetry::Core::TokenImport.load
|
|
104
|
+
# dispatches by extension (.md -> DesignMd.parse, .json -> Figma/DTCG variables,
|
|
105
|
+
# .css -> Paper/CSS custom properties); everything downstream is the pipeline
|
|
106
|
+
# poetry:design:import already ran, so nothing new decides what ships.
|
|
107
|
+
def poetry_design_run_import(source, task:)
|
|
108
|
+
abort "#{task}: pass the file - bin/rails '#{task}[path]'" unless source
|
|
109
|
+
abort "#{task}: no such file #{source}" unless File.exist?(source)
|
|
110
|
+
|
|
111
|
+
doc = Poetry::Core::TokenImport.load(source)
|
|
112
|
+
plan = Poetry::Core::DesignMd::Import.new.plan(doc, force: ENV["POETRY_DESIGN_FORCE"] == "1")
|
|
113
|
+
|
|
114
|
+
written = nil
|
|
115
|
+
if plan.any_overrides?
|
|
116
|
+
written = Rails.root.join("app/assets/tailwind/poetry/design-overrides.css")
|
|
117
|
+
written.write(poetry_design_overrides_css(plan, File.basename(source)))
|
|
118
|
+
poetry_design_wire_overrides
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if ENV["POETRY_DESIGN_JSON"] == "1"
|
|
122
|
+
puts JSON.pretty_generate(poetry_design_report_json(plan, written))
|
|
123
|
+
else
|
|
124
|
+
puts poetry_design_report(plan, written)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The declared .cn-* overrides from the host manifest - tolerant
|
|
129
|
+
# like every other manifest reader (missing file/key = none declared).
|
|
130
|
+
def poetry_design_declared_overrides
|
|
131
|
+
path = Rails.root.join("config/poetry_components.yml")
|
|
132
|
+
# Hosts write `created:` as a bare YAML date - permit it, then normalize
|
|
133
|
+
# to ISO strings so everything downstream (scan, DESIGN.md) is string-typed.
|
|
134
|
+
config = path.exist? ? YAML.safe_load_file(path, permitted_classes: [Date]) : nil
|
|
135
|
+
return [] unless config.is_a?(Hash) && config["overrides"].is_a?(Array)
|
|
136
|
+
|
|
137
|
+
config["overrides"].map do |entry|
|
|
138
|
+
entry.is_a?(Hash) ? entry.merge("created" => entry["created"]&.to_s) : entry
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Host-owned stylesheet SOURCES: everything the app authors, excluding the
|
|
143
|
+
# poetry-vendored set (which legitimately defines cn-*) and compiled output.
|
|
144
|
+
def poetry_design_host_css_sources
|
|
145
|
+
root = Rails.root
|
|
146
|
+
sources = {}
|
|
147
|
+
root.glob("app/assets/**/*.css").each do |path|
|
|
148
|
+
relative = path.relative_path_from(root).to_s
|
|
149
|
+
next if relative.start_with?("app/assets/tailwind/poetry/", "app/assets/builds/")
|
|
150
|
+
|
|
151
|
+
sources[relative] = path.read
|
|
152
|
+
end
|
|
153
|
+
sources
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# The installed theme, recovered from the slot bytes themselves (the
|
|
157
|
+
# installer vendors the chosen fragment verbatim into the style-default.css
|
|
158
|
+
# slot) - "custom" when the slot was hand-edited past recognition.
|
|
159
|
+
def poetry_design_installed_theme
|
|
160
|
+
slot = Rails.root.join("app/assets/tailwind/poetry/style-default.css")
|
|
161
|
+
abort "poetry:design:export: no poetry install found (#{slot} missing) - run `bin/rails g poetry:install`" \
|
|
162
|
+
unless slot.exist?
|
|
163
|
+
|
|
164
|
+
bytes = slot.read
|
|
165
|
+
Poetry::Ui::Themes.names.find { |name| Poetry::Ui.root.join("themes/#{name}.css").read == bytes } || "custom"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# The vendored tokens.css may have been replaced wholesale (the shadcn
|
|
169
|
+
# drop-in contract) - this export reads the gem's DTCG source, so say so
|
|
170
|
+
# rather than silently exporting values the app no longer paints.
|
|
171
|
+
def poetry_design_warn_on_token_drift
|
|
172
|
+
host_tokens = Rails.root.join("app/assets/tailwind/poetry/tokens.css")
|
|
173
|
+
return unless host_tokens.exist?
|
|
174
|
+
return if host_tokens.read == Poetry::Core.root.join("tokens/tokens.css").read
|
|
175
|
+
|
|
176
|
+
puts "poetry:design:export: NOTE - app/assets/tailwind/poetry/tokens.css diverges from the gem " \
|
|
177
|
+
"defaults (drop-in theme or hand edits); this export lists the gem's token values"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Exports must tell the truth AFTER an import: fold the app's
|
|
181
|
+
# design-overrides.css (our own generated format) back into the document
|
|
182
|
+
# so the exported DESIGN.md carries the values the app actually paints.
|
|
183
|
+
def poetry_design_apply_overrides(doc)
|
|
184
|
+
path = Rails.root.join("app/assets/tailwind/poetry/design-overrides.css")
|
|
185
|
+
return unless path.exist?
|
|
186
|
+
|
|
187
|
+
path.read.scan(/^(:root|\.dark)\s*\{([^}]*)\}/m).each do |selector, body|
|
|
188
|
+
mode = selector == ":root" ? "light" : "dark"
|
|
189
|
+
body.scan(/--([a-z0-9-]+):\s*([^;]+);/).each do |name, value|
|
|
190
|
+
if name == "radius"
|
|
191
|
+
doc["radius"] = value.strip if mode == "light"
|
|
192
|
+
elsif doc["colors"][mode].key?(name) && (color = Poetry::Core::Tokens::Color.parse(value.strip))
|
|
193
|
+
doc["colors"][mode][name] = color
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
puts "poetry:design:export: includes app/assets/tailwind/poetry/design-overrides.css values"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# The overrides stylesheet: :root carries the imported light values (+
|
|
201
|
+
# radius); .dark carries imported dark values plus the poetry-default PINS
|
|
202
|
+
# for light-overridden names the source left dark-silent (without them the
|
|
203
|
+
# later :root block would also win in dark mode - equal specificity).
|
|
204
|
+
def poetry_design_overrides_css(plan, source_name)
|
|
205
|
+
lines = ["/* Generated by `bin/rails poetry:design:import` from #{source_name} - do not edit.",
|
|
206
|
+
" Loads AFTER the poetry theme imports so these blocks win the cascade;",
|
|
207
|
+
" re-run the import to regenerate. */", ""]
|
|
208
|
+
lines << ":root {"
|
|
209
|
+
lines << " --radius: #{plan.radius};" if plan.radius
|
|
210
|
+
plan.overrides["light"].sort.each { |name, color| lines << " --#{name}: #{color.css};" }
|
|
211
|
+
lines << "}"
|
|
212
|
+
|
|
213
|
+
dark = plan.overrides["dark"].sort + plan.pins.sort
|
|
214
|
+
if dark.any?
|
|
215
|
+
lines << "" << ".dark {"
|
|
216
|
+
plan.overrides["dark"].sort.each { |name, color| lines << " --#{name}: #{color.css};" }
|
|
217
|
+
if plan.pins.any?
|
|
218
|
+
lines << " /* pinned to the poetry defaults (source had no dark palette) */"
|
|
219
|
+
plan.pins.sort.each { |name, color| lines << " --#{name}: #{color.css};" }
|
|
220
|
+
end
|
|
221
|
+
lines << "}"
|
|
222
|
+
end
|
|
223
|
+
"#{lines.join("\n")}\n"
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Idempotently add the overrides import after the last top-level ./poetry/
|
|
227
|
+
# import in the Tailwind entry (later = wins at equal specificity).
|
|
228
|
+
def poetry_design_wire_overrides
|
|
229
|
+
entry = Rails.root.join("app/assets/tailwind/application.css")
|
|
230
|
+
import_line = %(@import "./poetry/design-overrides.css";)
|
|
231
|
+
unless entry.exist?
|
|
232
|
+
puts "poetry:design:import: NOTE - no #{entry}; add #{import_line} to your Tailwind entry yourself"
|
|
233
|
+
return
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
lines = entry.read.lines
|
|
237
|
+
return if lines.any? { |line| line.start_with?(import_line) }
|
|
238
|
+
|
|
239
|
+
last = lines.rindex { |line| line.match?(%r{\A@(?:import|source) "\./poetry/}) }
|
|
240
|
+
if last
|
|
241
|
+
lines.insert(last + 1, "#{import_line}\n")
|
|
242
|
+
entry.write(lines.join)
|
|
243
|
+
puts "poetry:design:import: wired #{import_line} into app/assets/tailwind/application.css"
|
|
244
|
+
else
|
|
245
|
+
puts "poetry:design:import: NOTE - no ./poetry/ imports found in #{entry}; " \
|
|
246
|
+
"add #{import_line} after them yourself"
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def poetry_design_report(plan, written)
|
|
251
|
+
out = []
|
|
252
|
+
plan.overrides.each do |mode, colors|
|
|
253
|
+
next if colors.empty?
|
|
254
|
+
|
|
255
|
+
out << (" applied (#{mode}): " + colors.sort.map { |name, color| "#{name} #{color.css}" }.join(", "))
|
|
256
|
+
end
|
|
257
|
+
out << " radius: #{plan.radius}" if plan.radius
|
|
258
|
+
out << " pinned (dark, poetry defaults): #{plan.pins.keys.sort.join(", ")}" if plan.pins.any?
|
|
259
|
+
if plan.contrast.any?
|
|
260
|
+
out << " contrast (merged set, AA floor):"
|
|
261
|
+
plan.contrast.each { |result| out << " #{result}" }
|
|
262
|
+
end
|
|
263
|
+
if plan.dropped.any?
|
|
264
|
+
out << " dropped (never guessed):"
|
|
265
|
+
plan.dropped.each { |drop| out << " #{drop.name}: #{drop.reason}" }
|
|
266
|
+
end
|
|
267
|
+
out << " #{plan.typography_note}" if plan.typography_note
|
|
268
|
+
out << if written
|
|
269
|
+
"wrote #{written.relative_path_from(Rails.root)}"
|
|
270
|
+
else
|
|
271
|
+
"no overrides written (nothing importable survived the gate)"
|
|
272
|
+
end
|
|
273
|
+
out.join("\n")
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def poetry_design_report_json(plan, written)
|
|
277
|
+
{
|
|
278
|
+
"applied" => plan.applied.map do |a|
|
|
279
|
+
{ "role" => a.role, "from" => a.from, "mode" => a.mode, "css" => a.color.css }
|
|
280
|
+
end,
|
|
281
|
+
"pins" => plan.pins.transform_values(&:css),
|
|
282
|
+
"radius" => plan.radius,
|
|
283
|
+
"contrast" => plan.contrast.map do |r|
|
|
284
|
+
{ "mode" => r.mode, "pair" => r.label, "ratio" => r.ratio.round(2), "pass" => r.pass,
|
|
285
|
+
"suggestion" => r.suggestion, "shipped_failing" => r.shipped }.compact
|
|
286
|
+
end,
|
|
287
|
+
"dropped" => plan.dropped.map do |d|
|
|
288
|
+
{ "name" => d.name, "value" => d.value, "mode" => d.mode, "reason" => d.reason }.compact
|
|
289
|
+
end,
|
|
290
|
+
"typography_note" => plan.typography_note,
|
|
291
|
+
"written" => written&.relative_path_from(Rails.root)&.to_s
|
|
292
|
+
}.compact
|
|
293
|
+
end
|