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,56 @@
|
|
|
1
|
+
# paper - bring a paper.design canvas into poetry
|
|
2
|
+
|
|
3
|
+
Paper's canvas is real HTML + CSS, and its tokens are CSS custom properties
|
|
4
|
+
on the same axes poetry themes use, so it speaks poetry's dialect directly.
|
|
5
|
+
Two doors, independent - a theme handoff, and a canvas-to-components handoff.
|
|
6
|
+
Translate INTO poetry primitives and tokens; never transcribe raw styles.
|
|
7
|
+
|
|
8
|
+
## Door 1: theme -> an AA-gated theme (no MCP needed)
|
|
9
|
+
|
|
10
|
+
1. In Paper, run **Copy theme** and paste the CSS into a file (e.g.
|
|
11
|
+
`paper-theme.css`).
|
|
12
|
+
2. Run `bin/rails poetry:paper:import[paper-theme.css]` (or
|
|
13
|
+
`poetry:design:import` - it detects `.css`).
|
|
14
|
+
3. **Read the report.** The importer parses the `:root` / `@theme` custom
|
|
15
|
+
properties (stripping a Tailwind `color-` namespace), resolves `var()`
|
|
16
|
+
references, converts values into poetry's OKLCH model, then measures every
|
|
17
|
+
touched pair against WCAG AA. A value that fails AA is **dropped and
|
|
18
|
+
reported with the nearest passing suggestion** - never shipped; a property
|
|
19
|
+
with no poetry role is dropped and listed, never guessed.
|
|
20
|
+
4. It writes `app/assets/tailwind/poetry/design-overrides.css` and wires the
|
|
21
|
+
import. Rebuild CSS. `POETRY_DESIGN_FORCE=1` / `POETRY_DESIGN_JSON=1` as
|
|
22
|
+
with any import.
|
|
23
|
+
|
|
24
|
+
Copy theme is a one-shot copy, not a live sync - re-run the import when the
|
|
25
|
+
Paper theme changes.
|
|
26
|
+
|
|
27
|
+
## Door 2: a canvas -> components (dual-MCP)
|
|
28
|
+
|
|
29
|
+
Needs the **Paper MCP** connected alongside the poetry MCP. Paper's MCP ships
|
|
30
|
+
with the Paper Desktop app and runs locally once a file is open; register it
|
|
31
|
+
once: `claude mcp add paper --transport http http://127.0.0.1:29979/mcp
|
|
32
|
+
--scope user`. The free tier's 100 tool calls/week is enough for a handoff.
|
|
33
|
+
|
|
34
|
+
Flow:
|
|
35
|
+
|
|
36
|
+
1. Read the design - `get_selection` / `get_tree_summary` for structure,
|
|
37
|
+
`get_jsx` (ask for the Tailwind form) and `get_computed_styles` for the
|
|
38
|
+
styling, `get_screenshot` to keep layout fidelity.
|
|
39
|
+
2. Land the tokens first via Door 1 so Paper's variables line up with poetry
|
|
40
|
+
theme tokens.
|
|
41
|
+
3. Translate, do not copy. Map each Paper subtree to the nearest poetry
|
|
42
|
+
primitive - consult `list_components` / `describe_component`; assemble with
|
|
43
|
+
`compose` / `build_page`, never hand-authored markup.
|
|
44
|
+
4. Run poetry `check` LAST - a PASS verdict, not an eyeball.
|
|
45
|
+
|
|
46
|
+
Mapping guide:
|
|
47
|
+
|
|
48
|
+
- Artboard -> a poetry block or page.
|
|
49
|
+
- Node subtree -> the nearest poetry component.
|
|
50
|
+
- Tailwind classes from `get_jsx` -> poetry variants and theme tokens, not
|
|
51
|
+
copied class strings.
|
|
52
|
+
- Computed styles -> poetry theme tokens (Door 1), never per-instance CSS.
|
|
53
|
+
|
|
54
|
+
Optional round-trip: push a rendered poetry component back into Paper as
|
|
55
|
+
editable layers with `write_html`, so a designer can review poetry's output
|
|
56
|
+
on the canvas. Bank this as a review loop, not a build step.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# study - extract a brand into a DESIGN.md poetry can import
|
|
2
|
+
|
|
3
|
+
`study` turns a brand surface (a URL, screenshots, an existing style guide)
|
|
4
|
+
into the design-skill ecosystem's shared artifact: a DESIGN.md. poetry both
|
|
5
|
+
writes and reads the format, so the loop is:
|
|
6
|
+
|
|
7
|
+
study the brand -> DESIGN.md -> bin/rails "poetry:design:import[DESIGN.md]"
|
|
8
|
+
|
|
9
|
+
## Writing the DESIGN.md
|
|
10
|
+
|
|
11
|
+
Front-matter carries the machine-readable part:
|
|
12
|
+
|
|
13
|
+
- Flat `colors:` = the LIGHT mode palette, role-named (`brand`, `surface`,
|
|
14
|
+
`text`, `accent`, ...). poetry's importer maps roles conservatively
|
|
15
|
+
(`brand` -> `--primary`, `surface` -> `--card`, `text` -> `--foreground`,
|
|
16
|
+
Material-style `on-*` -> `*-foreground`) and reports every mapping.
|
|
17
|
+
- `modes.dark:` for dark values when the brand defines them. A light-only
|
|
18
|
+
study is fine: poetry PINS dark to its shipped defaults rather than
|
|
19
|
+
letting light values bleed into dark mode.
|
|
20
|
+
- `rounded:` resolved to px; typography as metadata (family names, pairing
|
|
21
|
+
intent) - fonts never enter CSS via import.
|
|
22
|
+
- Markdown sections (voice, imagery, spacing notes) carry the prose the
|
|
23
|
+
front-matter can't; the importer ignores what it can't parse and REPORTS
|
|
24
|
+
it rather than guessing.
|
|
25
|
+
|
|
26
|
+
**Extract, never fabricate.** Record only what the brand actually shows.
|
|
27
|
+
If the brand has no dark mode, don't invent one; if you can't find a
|
|
28
|
+
motion language, omit the section. Dropped-with-a-note beats guessed - the
|
|
29
|
+
import report is the honest ledger.
|
|
30
|
+
|
|
31
|
+
## What import will do with it
|
|
32
|
+
|
|
33
|
+
Role-mapped token overrides, WCAG AA enforced on the merged set: failing
|
|
34
|
+
pairs are DROPPED with a deterministic nearest-AA suggestion (an OKLCH
|
|
35
|
+
lightness walk, chroma held), and `POETRY_DESIGN_FORCE=1` is the only way
|
|
36
|
+
a failing pair ships. Expect the report to move some values - a brand hex
|
|
37
|
+
that fails AA against white comes back as the nearest passing neighbor,
|
|
38
|
+
which is usually what the brand's own accessible surfaces do anyway.
|
|
39
|
+
|
|
40
|
+
## redesign - same content, different fingerprint
|
|
41
|
+
|
|
42
|
+
`redesign` keeps copy, information architecture, and brand meaning, and
|
|
43
|
+
swaps the visual fingerprint: pick a different shipped theme
|
|
44
|
+
(`references/theme.md`), re-run the brand's DESIGN.md on top if one
|
|
45
|
+
exists, and re-audit. Nothing in the templates should need to change - if
|
|
46
|
+
a redesign forces template edits, the original leaned on per-instance
|
|
47
|
+
styling it shouldn't have had.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# theme - pick, adapt, or import a visual fingerprint
|
|
2
|
+
|
|
3
|
+
One visual theme per app, chosen at install: `bin/rails g poetry:install
|
|
4
|
+
--theme <name>` (re-runnable; the chosen fragment's bytes land in
|
|
5
|
+
`app/assets/tailwind/poetry/style-default.css`). Components read tokens and
|
|
6
|
+
`cn-*` names; the theme owns every visual decision, so switching themes is
|
|
7
|
+
a reinstall, not a refactor.
|
|
8
|
+
|
|
9
|
+
## The nine shipped themes
|
|
10
|
+
|
|
11
|
+
| Theme | Character |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `default` | poetry's original look - the new-york-v4 lineage the library was built against |
|
|
14
|
+
| `vega` | closest to default: ring-based surfaces, subtle blur scrims, xl radii |
|
|
15
|
+
| `nova` | compact - tighter paddings and smaller surfaces |
|
|
16
|
+
| `mira` | compact and quiet - relaxed xs body text, heavily dimmed drawer scrims |
|
|
17
|
+
| `rhea` | soft and elevated - capped 4xl radii, shadow-xl, blurred scrims |
|
|
18
|
+
| `maia` | fully rounded - 4xl surfaces, deeply dimmed scrims |
|
|
19
|
+
| `luma` | soft-round - blurred scrims and a floating drawer frame |
|
|
20
|
+
| `lyra` | boxy and sharp - radius zero, type one step down, made for mono fonts |
|
|
21
|
+
| `sera` | editorial - uppercase tracked type, underline-only fields, serif pairing |
|
|
22
|
+
|
|
23
|
+
Choosing by brief, not by taste-in-a-vacuum:
|
|
24
|
+
|
|
25
|
+
- Dense operational tools (admin, monitoring, tables everywhere): `nova` or
|
|
26
|
+
`mira` - the compact surfaces buy rows.
|
|
27
|
+
- Consumer/marketing warmth: `rhea`, `maia`, or `luma` - rounded, elevated,
|
|
28
|
+
soft scrims.
|
|
29
|
+
- Technical/developer products: `lyra` - sharp, mono-biased, zero radius.
|
|
30
|
+
- Editorial/content products: `sera` - tracked uppercase labels, serif
|
|
31
|
+
pairing, underline fields.
|
|
32
|
+
- `default` is the stock look. Picking it is legitimate but should be a
|
|
33
|
+
DECISION - when a brief implies any character at all, a non-default theme
|
|
34
|
+
is usually the stronger answer (the design-lint `stock-theme-nudge` rule
|
|
35
|
+
exists for exactly this).
|
|
36
|
+
|
|
37
|
+
## Fonts ride the theme as metadata
|
|
38
|
+
|
|
39
|
+
No poetry theme moves a font token - `lyra` is biased hard to mono
|
|
40
|
+
(JetBrains Mono; radius forced to zero) and `sera` keeps serif company
|
|
41
|
+
(Noto Serif, Instrument Serif), but both express their typography through
|
|
42
|
+
size, weight, tracking, and uppercase utilities. The font-family itself is
|
|
43
|
+
the app's call: add the family in the app's own CSS/font pipeline when
|
|
44
|
+
installing lyra or sera.
|
|
45
|
+
|
|
46
|
+
## Status color vocabulary
|
|
47
|
+
|
|
48
|
+
The token set carries `--success`, `--warning`, and `--info` alongside
|
|
49
|
+
`--destructive` (all four AA-managed in both modes). Badges ship soft
|
|
50
|
+
status variants on them. Use the set; never invent status colors from
|
|
51
|
+
palette utilities.
|
|
52
|
+
|
|
53
|
+
## The DESIGN.md door (brand import/export)
|
|
54
|
+
|
|
55
|
+
- `bin/rails poetry:design:export` writes this app's DESIGN.md (tokens +
|
|
56
|
+
treatment; `POETRY_DESIGN_PATH` to target elsewhere, `POETRY_DESIGN_FORCE=1`
|
|
57
|
+
to overwrite a non-poetry file). Run it so external design skills can read
|
|
58
|
+
the app's current design.
|
|
59
|
+
- `bin/rails "poetry:design:import[path/to/DESIGN.md]"` plans role-mapped
|
|
60
|
+
token overrides from a foreign DESIGN.md (a brand study, or an export
|
|
61
|
+
from another design tool): conservative role aliases, WCAG AA enforced on the
|
|
62
|
+
MERGED set, failing pairs dropped with a deterministic nearest-AA
|
|
63
|
+
suggestion, dark values pinned when the source is light-only. The report
|
|
64
|
+
lists every mapping and every drop (`POETRY_DESIGN_JSON=1` for JSON);
|
|
65
|
+
`POETRY_DESIGN_FORCE=1` is the only way a failing pair ships.
|
|
66
|
+
- Overrides land in `app/assets/tailwind/poetry/design-overrides.css` - the
|
|
67
|
+
one sanctioned place brand values live outside the theme fragment.
|
|
68
|
+
|
|
69
|
+
Adapting a theme = shipped theme + DESIGN.md overrides on top. Full brand
|
|
70
|
+
work = `study` (see `references/study.md`) then import.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Generators
|
|
5
|
+
# The Claude Code skills, shared by poetry:install and the standalone
|
|
6
|
+
# poetry:skill (the refresh path - re-run after updating poetry gems):
|
|
7
|
+
#
|
|
8
|
+
# .claude/skills/poetry/ GENERATED from the live registry
|
|
9
|
+
# .claude/skills/poetry-design/ curated taste layer (static templates)
|
|
10
|
+
# .claude/skills/poetry-component/ authoring layer: anatomy, docs
|
|
11
|
+
# standard, audit checklist (static)
|
|
12
|
+
#
|
|
13
|
+
# Same Thor note as AgentsSection: only methods on the generator class
|
|
14
|
+
# register as steps, so each generator declares its own public step and
|
|
15
|
+
# calls apply_poetry_skills.
|
|
16
|
+
#
|
|
17
|
+
# @api private
|
|
18
|
+
module SkillsSection
|
|
19
|
+
DESIGN_TEMPLATES = File.expand_path("skill/templates/poetry-design", __dir__)
|
|
20
|
+
COMPONENT_TEMPLATES = File.expand_path("skill/templates/poetry-component", __dir__)
|
|
21
|
+
|
|
22
|
+
# Writes or refreshes the three skill directories under
|
|
23
|
+
# .claude/skills/.
|
|
24
|
+
def apply_poetry_skills
|
|
25
|
+
Poetry::Ui.skill_files.each do |relative, content|
|
|
26
|
+
create_file ".claude/skills/poetry/#{relative}", content
|
|
27
|
+
end
|
|
28
|
+
design_skill_files.each do |relative, content|
|
|
29
|
+
create_file ".claude/skills/poetry-design/#{relative}", content
|
|
30
|
+
end
|
|
31
|
+
component_skill_files.each do |relative, content|
|
|
32
|
+
create_file ".claude/skills/poetry-component/#{relative}", content
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Path => content for the curated skills - the eval harness writes
|
|
37
|
+
# these without a Thor context (the agents_section_text pattern:
|
|
38
|
+
# Object.new.extend(SkillsSection).design_skill_files).
|
|
39
|
+
def design_skill_files
|
|
40
|
+
static_skill_files(DESIGN_TEMPLATES)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Path => content for the poetry-component authoring skill (static
|
|
44
|
+
# templates).
|
|
45
|
+
def component_skill_files
|
|
46
|
+
static_skill_files(COMPONENT_TEMPLATES)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Path => content for every .md under a template root.
|
|
50
|
+
def static_skill_files(templates)
|
|
51
|
+
root = Pathname(templates)
|
|
52
|
+
Dir.glob(root.join("**/*.md").to_s).to_h do |file|
|
|
53
|
+
[Pathname(file).relative_path_from(root).to_s, File.read(file)]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# The chat replay DSL: script a user/assistant conversation in
|
|
6
|
+
# Ruby, get a DETERMINISTIC timeline of
|
|
7
|
+
# streaming frames to replay through the real Turbo Stream pipeline -
|
|
8
|
+
# no model, no network, fixed ids, fixed pacing. The DSL is pure data
|
|
9
|
+
# (transcript + timing); rendering frames into Message rows is the
|
|
10
|
+
# consumer's job (the docs replay demo is the reference consumer).
|
|
11
|
+
#
|
|
12
|
+
# A tool with `approval: true` PAUSES its segment after the input frame;
|
|
13
|
+
# the frames that follow (its output - or denial - and later parts)
|
|
14
|
+
# belong to the continuation and are produced by
|
|
15
|
+
# `continuation_frames(approved:)` - the human-in-the-loop
|
|
16
|
+
# model, server-shaped: the pause is a rendered form, the continuation
|
|
17
|
+
# is the stream after the decision.
|
|
18
|
+
#
|
|
19
|
+
# @example Scripting a turn with a tool call
|
|
20
|
+
# script = Poetry::Ui::Chat.script do
|
|
21
|
+
# user "What's the weather in Tokyo?"
|
|
22
|
+
# assistant do |w|
|
|
23
|
+
# w.reasoning "Check the weather tool first."
|
|
24
|
+
# w.tool("getWeather", input: { city: "Tokyo" }, sleep_ms: 800,
|
|
25
|
+
# output: { temp: 21, condition: "clear" })
|
|
26
|
+
# w.text "Clear skies and 21°C in Tokyo."
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
# script.segments # => enumerable segments; assistant segments carry
|
|
30
|
+
# # frames (accumulated part states + sleeps + versions)
|
|
31
|
+
module Chat
|
|
32
|
+
# Builds a Script from the block - instance_eval'd, so bare `user`
|
|
33
|
+
# and `assistant` calls declare the segments in transcript order.
|
|
34
|
+
#
|
|
35
|
+
# @return [Script] the compiled, deterministic script
|
|
36
|
+
def self.script(&)
|
|
37
|
+
Script.new(&)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# One rendered state of an assistant row mid-stream.
|
|
41
|
+
Frame = Struct.new(:parts, :sleep_ms, :version, keyword_init: true)
|
|
42
|
+
|
|
43
|
+
# A step of the conversation: :user (text) or :assistant (frames,
|
|
44
|
+
# possibly pausing for approval).
|
|
45
|
+
class Segment
|
|
46
|
+
# kind (:user or :assistant), the deterministic DOM id, the user
|
|
47
|
+
# text, and the AssistantTurn (assistant segments only).
|
|
48
|
+
attr_reader :kind, :id, :text, :turn
|
|
49
|
+
|
|
50
|
+
def initialize(kind:, id:, text: nil, turn: nil)
|
|
51
|
+
@kind = kind
|
|
52
|
+
@id = id
|
|
53
|
+
@text = text
|
|
54
|
+
@turn = turn
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Whether this segment stops at an approval pause.
|
|
58
|
+
#
|
|
59
|
+
# @return [Boolean]
|
|
60
|
+
def pause? = kind == :assistant && turn.pause?
|
|
61
|
+
|
|
62
|
+
# The frame timeline up to (and including) an approval pause, or
|
|
63
|
+
# the whole turn when nothing pauses.
|
|
64
|
+
#
|
|
65
|
+
# @return [Array<Frame>]
|
|
66
|
+
def frames = turn.frames
|
|
67
|
+
|
|
68
|
+
# The frames after the approval pause, resolved by the decision.
|
|
69
|
+
#
|
|
70
|
+
# @param approved [Boolean] the human decision at the pause
|
|
71
|
+
# @return [Array<Frame>]
|
|
72
|
+
def continuation_frames(approved:) = turn.continuation_frames(approved: approved)
|
|
73
|
+
|
|
74
|
+
# The final resting parts (approval segments resolve per decision).
|
|
75
|
+
#
|
|
76
|
+
# @return [Array<Hash>] the part list as last rendered
|
|
77
|
+
def final_parts(approved: true)
|
|
78
|
+
return [{ kind: :text, text: text }] if kind == :user
|
|
79
|
+
return frames.last.parts unless pause?
|
|
80
|
+
|
|
81
|
+
continuation_frames(approved: approved).last.parts
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The DSL root: collects user/assistant segments in scripted order
|
|
86
|
+
# and assigns each a deterministic id.
|
|
87
|
+
class Script
|
|
88
|
+
# Per-chunk streaming delay (ms) when a text part names none.
|
|
89
|
+
DEFAULT_TEXT_DELAY_MS = 30
|
|
90
|
+
# Words per streamed text chunk in the compiled frames.
|
|
91
|
+
TEXT_CHUNK_WORDS = 3
|
|
92
|
+
|
|
93
|
+
def initialize(&)
|
|
94
|
+
@segments = []
|
|
95
|
+
@counter = 0
|
|
96
|
+
instance_eval(&)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Declares a user message at this point in the transcript.
|
|
100
|
+
#
|
|
101
|
+
# @param text [String] the user's message
|
|
102
|
+
def user(text)
|
|
103
|
+
@segments << Segment.new(kind: :user, id: next_id, text: text)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Declares an assistant turn: pass plain text, or take the block
|
|
107
|
+
# form and compose text / reasoning / tool parts in order.
|
|
108
|
+
#
|
|
109
|
+
# @param text [String, nil] shortcut for a single text part
|
|
110
|
+
# @yieldparam writer [Writer] appends parts to the turn
|
|
111
|
+
def assistant(text = nil, &block)
|
|
112
|
+
turn = AssistantTurn.new
|
|
113
|
+
if block
|
|
114
|
+
yield(turn.writer)
|
|
115
|
+
elsif text
|
|
116
|
+
turn.writer.text(text)
|
|
117
|
+
end
|
|
118
|
+
@segments << Segment.new(kind: :assistant, id: next_id, turn: turn)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [Array<Segment>] the declared segments, in scripted order
|
|
122
|
+
attr_reader :segments
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def next_id
|
|
127
|
+
@counter += 1
|
|
128
|
+
"chat-msg-#{@counter}"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Collects writer calls into an ordered part list, then compiles the
|
|
133
|
+
# deterministic frame timeline.
|
|
134
|
+
class AssistantTurn
|
|
135
|
+
def initialize
|
|
136
|
+
@parts = []
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# The Writer collecting this turn's parts (memoized - `assistant`
|
|
140
|
+
# yields it).
|
|
141
|
+
#
|
|
142
|
+
# @return [Writer]
|
|
143
|
+
def writer = @writer ||= Writer.new(@parts)
|
|
144
|
+
|
|
145
|
+
# Whether any tool part requests approval (pausing the segment).
|
|
146
|
+
#
|
|
147
|
+
# @return [Boolean]
|
|
148
|
+
def pause? = @parts.any? { |part| part[:kind] == :tool && part[:approval] }
|
|
149
|
+
|
|
150
|
+
# Frames up to (and including) the pause, or the whole turn.
|
|
151
|
+
#
|
|
152
|
+
# @return [Array<Frame>]
|
|
153
|
+
def frames
|
|
154
|
+
compile(@parts.take_while.with_index { |_part, i| i.zero? || !pause_before?(i) })
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Frames AFTER the pause point, resolved by the decision. Versions
|
|
158
|
+
# continue from the paused prefix so the morph guard stays monotonic.
|
|
159
|
+
# Parts scripted AFTER an approval tool are the APPROVED path
|
|
160
|
+
# (denial streams tool-output-denied and stops;
|
|
161
|
+
# the approved branch carries the follow-through). So a denied
|
|
162
|
+
# continuation is exactly the resolution frame.
|
|
163
|
+
#
|
|
164
|
+
# @param approved [Boolean] the human decision at the pause
|
|
165
|
+
# @return [Array<Frame>]
|
|
166
|
+
def continuation_frames(approved:)
|
|
167
|
+
raise Error, "turn has no approval pause" unless pause?
|
|
168
|
+
|
|
169
|
+
resolved = @parts.map do |part|
|
|
170
|
+
next part unless part[:kind] == :tool && part[:approval]
|
|
171
|
+
|
|
172
|
+
part.merge(state: approved ? :done : :denied,
|
|
173
|
+
output: approved ? part[:output] : nil)
|
|
174
|
+
end
|
|
175
|
+
keep = approved ? resolved : resolved.take(pause_index + 1)
|
|
176
|
+
compile(keep).drop(frames.length)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private
|
|
180
|
+
|
|
181
|
+
def pause_index
|
|
182
|
+
@parts.index { |part| part[:kind] == :tool && part[:approval] }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def pause_before?(index)
|
|
186
|
+
before = @parts.first(index)
|
|
187
|
+
before.any? { |part| part[:kind] == :tool && part[:approval] }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# The accumulated-state timeline: every frame is the FULL part list
|
|
191
|
+
# as rendered at that instant (streaming morphs the same row).
|
|
192
|
+
def compile(parts)
|
|
193
|
+
frames = []
|
|
194
|
+
acc = []
|
|
195
|
+
version = 0
|
|
196
|
+
push = lambda do |sleep_ms|
|
|
197
|
+
version += 1
|
|
198
|
+
frames << Frame.new(parts: acc.map(&:dup), sleep_ms: sleep_ms, version: version)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
parts.each do |part|
|
|
202
|
+
case part[:kind]
|
|
203
|
+
when :reasoning, :text
|
|
204
|
+
chunks = part[:text].split(/(?<=\S)\s+/).each_slice(Script::TEXT_CHUNK_WORDS).map { |w| w.join(" ") }
|
|
205
|
+
delay = part.fetch(:delay_ms, Script::DEFAULT_TEXT_DELAY_MS) || Script::DEFAULT_TEXT_DELAY_MS
|
|
206
|
+
acc << part.merge(text: "")
|
|
207
|
+
chunks.each do |chunk|
|
|
208
|
+
acc[-1] = acc[-1].merge(text: [acc[-1][:text], chunk].reject(&:empty?).join(" "))
|
|
209
|
+
push.call(delay)
|
|
210
|
+
end
|
|
211
|
+
when :tool
|
|
212
|
+
acc << part.merge(state: :loading, output: nil)
|
|
213
|
+
push.call(0)
|
|
214
|
+
# An approval tool always passes through :awaiting_approval -
|
|
215
|
+
# resolved or not - so the paused prefix and the resolved
|
|
216
|
+
# timeline share frame counts up to the pause and the
|
|
217
|
+
# continuation begins AT the resolution frame (a denial with
|
|
218
|
+
# nothing after it still streams its denial).
|
|
219
|
+
if part[:approval]
|
|
220
|
+
acc[-1] = acc[-1].merge(state: :awaiting_approval)
|
|
221
|
+
push.call(part.fetch(:sleep_ms, 0))
|
|
222
|
+
end
|
|
223
|
+
unless part[:approval] && part[:state].nil?
|
|
224
|
+
acc[-1] = acc[-1].merge(state: part[:state] || :done, output: part[:output])
|
|
225
|
+
push.call(part.fetch(:sleep_ms, 0))
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
frames
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# The builder handed to `assistant do |w| ... end` - each call
|
|
234
|
+
# appends one part (text / reasoning / tool) to the turn, in order.
|
|
235
|
+
class Writer
|
|
236
|
+
def initialize(parts)
|
|
237
|
+
@parts = parts
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Appends a streamed text part.
|
|
241
|
+
#
|
|
242
|
+
# @param text [String] the assistant prose
|
|
243
|
+
# @param delay_ms [Integer, nil] per-chunk delay override (defaults
|
|
244
|
+
# to Script::DEFAULT_TEXT_DELAY_MS)
|
|
245
|
+
# @return [Writer] self, for chaining
|
|
246
|
+
def text(text, delay_ms: nil)
|
|
247
|
+
@parts << { kind: :text, text: text, delay_ms: delay_ms }.compact
|
|
248
|
+
self
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Appends a reasoning part - streamed like text, rendered as the
|
|
252
|
+
# collapsible thinking block.
|
|
253
|
+
#
|
|
254
|
+
# @param text [String] the reasoning prose
|
|
255
|
+
# @param delay_ms [Integer, nil] per-chunk delay override
|
|
256
|
+
# @return [Writer] self, for chaining
|
|
257
|
+
def reasoning(text, delay_ms: nil)
|
|
258
|
+
@parts << { kind: :reasoning, text: text, delay_ms: delay_ms }.compact
|
|
259
|
+
self
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Appends a tool-call part: the input frame streams first (state
|
|
263
|
+
# :loading), then after sleep_ms the resolution frame carries the
|
|
264
|
+
# output. approval: true pauses the segment at the input frame for
|
|
265
|
+
# a human decision; denied: true scripts the denied resolution.
|
|
266
|
+
#
|
|
267
|
+
# @param name [String] the tool name as rendered
|
|
268
|
+
# @param input [Hash] the tool input payload
|
|
269
|
+
# @param output [Object, nil] the result shown on resolution
|
|
270
|
+
# @param sleep_ms [Integer] thinking time before the resolution
|
|
271
|
+
# @param approval [Boolean] pause for a human decision after input
|
|
272
|
+
# @param denied [Boolean] script the denied resolution
|
|
273
|
+
# @return [Writer] self, for chaining # -- the writer vocabulary mirrors the tool part's options
|
|
274
|
+
def tool(name, input:, output: nil, sleep_ms: 0, approval: false, denied: false)
|
|
275
|
+
@parts << { kind: :tool, name: name, input: input, output: output,
|
|
276
|
+
sleep_ms: sleep_ms, approval: approval,
|
|
277
|
+
state: denied ? :denied : nil }
|
|
278
|
+
self
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# The CodeBlock's rouge seam: highlighting is a SOFT capability -
|
|
6
|
+
# `gem "rouge"` in the host Gemfile turns it on; without it the component
|
|
7
|
+
# renders the plain escaped code unchanged (the fallback is simply
|
|
8
|
+
# "no rouge", and nothing shifts because the markup shape is identical).
|
|
9
|
+
module CodeBlockHighlighter
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Whether rouge is loadable in this host (memoized).
|
|
13
|
+
#
|
|
14
|
+
# @return [Boolean]
|
|
15
|
+
def available?
|
|
16
|
+
return @available if defined?(@available)
|
|
17
|
+
|
|
18
|
+
@available = begin
|
|
19
|
+
require "rouge"
|
|
20
|
+
true
|
|
21
|
+
rescue LoadError
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# -> html_safe highlighted markup - every line wrapped in .line (the
|
|
27
|
+
# counter hook), the requested ones also .hll (the theme's tint hook) -
|
|
28
|
+
# or nil when rouge is absent. Unknown languages lex as plain text.
|
|
29
|
+
def highlight(code, language:, highlight_lines: [])
|
|
30
|
+
return nil unless available?
|
|
31
|
+
|
|
32
|
+
lexer = Rouge::Lexer.find(language.to_s) || Rouge::Lexers::PlainText.new
|
|
33
|
+
formatter.new(highlight_lines: Array(highlight_lines).map(&:to_i))
|
|
34
|
+
.format(lexer.lex(code)).html_safe
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Rouge 5 asserts a PLAIN HTML delegate inside its line-wise wrappers
|
|
38
|
+
# (HTMLLinewise cannot wrap HTMLLineHighlighter), so poetry carries its
|
|
39
|
+
# own line formatter over the stable token_lines API.
|
|
40
|
+
def formatter
|
|
41
|
+
@formatter ||= Class.new(Rouge::Formatters::HTML) do
|
|
42
|
+
def initialize(highlight_lines: [])
|
|
43
|
+
super()
|
|
44
|
+
@highlight_lines = highlight_lines
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def stream(tokens)
|
|
48
|
+
token_lines(tokens).each.with_index(1) do |line_tokens, lineno|
|
|
49
|
+
classes = ["line"]
|
|
50
|
+
classes << "hll" if @highlight_lines.include?(lineno)
|
|
51
|
+
yield %(<span class="#{classes.join(" ")}">)
|
|
52
|
+
line_tokens.each { |token, value| yield span(token, value) }
|
|
53
|
+
yield "</span>\n"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/engine"
|
|
4
|
+
|
|
5
|
+
module Poetry
|
|
6
|
+
module Ui
|
|
7
|
+
# The Rails engine: wires the component classes, the poetry_* view
|
|
8
|
+
# helpers, the Turbo Stream toast action, and the preview paths into
|
|
9
|
+
# the host app.
|
|
10
|
+
class Engine < ::Rails::Engine
|
|
11
|
+
# Standard engine layout: app/components, app/helpers, and
|
|
12
|
+
# config/locales are picked up by Rails' engine path conventions.
|
|
13
|
+
config.autoload_paths << "#{Poetry::Ui.root}/app/components"
|
|
14
|
+
config.eager_load_paths << "#{Poetry::Ui.root}/app/components"
|
|
15
|
+
|
|
16
|
+
# ComponentsHelper lives in app/helpers (Zeitwerk-autoloaded), so unlike
|
|
17
|
+
# a lib/ helper it is NOT already loaded at gem-require time. A host gem
|
|
18
|
+
# that forces ActionView to load *during* initialization - e.g. an
|
|
19
|
+
# ActionText editor prepending to ActionView::Helpers::FormHelper
|
|
20
|
+
# - fires this on_load hook before the engine's
|
|
21
|
+
# app/helpers constant is resolvable, raising NameError at boot. Deferring
|
|
22
|
+
# the hook registration into a to_prepare block runs it only once autoload
|
|
23
|
+
# paths are wired (and re-runs safely on reload), so the constant always
|
|
24
|
+
# resolves. (poetry-core's TagHelper avoids this only because it lives in
|
|
25
|
+
# lib/ and is required eagerly.)
|
|
26
|
+
initializer "poetry_ui.helpers" do |app|
|
|
27
|
+
app.config.to_prepare do
|
|
28
|
+
ActiveSupport.on_load(:action_view) do
|
|
29
|
+
include Poetry::Ui::ComponentsHelper
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# turbo_stream.poetry_toast(...) - the canonical server-side toast.
|
|
35
|
+
# poetry-ui does not depend on turbo-rails; hosts that load it get
|
|
36
|
+
# the action through turbo's own load hook (a no-op otherwise).
|
|
37
|
+
initializer "poetry_ui.turbo_stream_actions" do
|
|
38
|
+
ActiveSupport.on_load(:turbo_streams_tag_builder) do
|
|
39
|
+
include Poetry::Ui::ToastStreamActions
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
initializer "poetry_ui.previews" do |app|
|
|
44
|
+
app.config.view_component.previews.paths << "#{Poetry::Ui.root}/app/components"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Lookbook is a dev-only dependency; guard so the engine never crashes
|
|
48
|
+
# a production (or lean test) host that does not load it (the
|
|
49
|
+
# poetry-core pattern).
|
|
50
|
+
initializer "poetry_ui.setup_lookbook" do |app|
|
|
51
|
+
app.config.lookbook.preview_paths << "#{Poetry::Ui.root}/app/components" if defined?(Lookbook)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|