unmagic-components 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -1
  3. data/README.md +506 -1
  4. data/app/assets/icons/lucide/LICENSE +43 -0
  5. data/app/assets/icons/lucide/arrow-down.svg +1 -0
  6. data/app/assets/icons/lucide/arrow-left.svg +1 -0
  7. data/app/assets/icons/lucide/arrow-up.svg +1 -0
  8. data/app/assets/icons/lucide/ban.svg +1 -0
  9. data/app/assets/icons/lucide/brain.svg +1 -0
  10. data/app/assets/icons/lucide/check.svg +1 -0
  11. data/app/assets/icons/lucide/chevron-down.svg +1 -0
  12. data/app/assets/icons/lucide/chevron-left.svg +1 -0
  13. data/app/assets/icons/lucide/chevron-right.svg +1 -0
  14. data/app/assets/icons/lucide/circle-check.svg +1 -0
  15. data/app/assets/icons/lucide/circle-dashed.svg +1 -0
  16. data/app/assets/icons/lucide/circle-question-mark.svg +1 -0
  17. data/app/assets/icons/lucide/circle-stop.svg +1 -0
  18. data/app/assets/icons/lucide/circle-x.svg +1 -0
  19. data/app/assets/icons/lucide/clock.svg +1 -0
  20. data/app/assets/icons/lucide/copy.svg +1 -0
  21. data/app/assets/icons/lucide/ellipsis-vertical.svg +1 -0
  22. data/app/assets/icons/lucide/file.svg +1 -0
  23. data/app/assets/icons/lucide/folder.svg +1 -0
  24. data/app/assets/icons/lucide/grip-vertical.svg +1 -0
  25. data/app/assets/icons/lucide/info.svg +1 -0
  26. data/app/assets/icons/lucide/lightbulb.svg +1 -0
  27. data/app/assets/icons/lucide/list-checks.svg +1 -0
  28. data/app/assets/icons/lucide/loader-circle.svg +1 -0
  29. data/app/assets/icons/lucide/messages-square.svg +1 -0
  30. data/app/assets/icons/lucide/paperclip.svg +1 -0
  31. data/app/assets/icons/lucide/pencil.svg +1 -0
  32. data/app/assets/icons/lucide/plus.svg +1 -0
  33. data/app/assets/icons/lucide/rotate-cw.svg +1 -0
  34. data/app/assets/icons/lucide/shield-alert.svg +1 -0
  35. data/app/assets/icons/lucide/timer.svg +1 -0
  36. data/app/assets/icons/lucide/triangle-alert.svg +1 -0
  37. data/app/assets/icons/lucide/wrench.svg +1 -0
  38. data/app/assets/icons/lucide/x.svg +1 -0
  39. data/app/assets/javascripts/unmagic/components/ai_chat.js +142 -0
  40. data/app/assets/javascripts/unmagic/components/autoscroll.js +103 -0
  41. data/app/assets/javascripts/unmagic/components/board.js +65 -0
  42. data/app/assets/javascripts/unmagic/components/dropzone.js +206 -0
  43. data/app/assets/javascripts/unmagic/components/elapsed.js +64 -0
  44. data/app/assets/javascripts/unmagic/components/optimistic.js +71 -0
  45. data/app/assets/javascripts/unmagic/components/placement.js +30 -0
  46. data/app/assets/javascripts/unmagic/components/slash_menu.js +186 -0
  47. data/app/assets/javascripts/unmagic/components/sortable.js +6 -0
  48. data/app/assets/javascripts/unmagic/components/sortable_item.js +122 -0
  49. data/app/assets/javascripts/unmagic/components/sortable_list.js +135 -0
  50. data/app/assets/javascripts/unmagic/components/sortable_param.js +14 -0
  51. data/app/assets/javascripts/unmagic/components/sortable_session.js +502 -0
  52. data/app/assets/javascripts/unmagic/components/streaming_markdown.js +269 -0
  53. data/app/assets/javascripts/unmagic/components/toasts.js +14 -4
  54. data/app/assets/javascripts/unmagic/components/toolbar.js +76 -0
  55. data/app/assets/javascripts/unmagic/components/upsert.js +4 -20
  56. data/app/assets/javascripts/unmagic/components.js +10 -0
  57. data/app/assets/tailwind/unmagic_components/engine.css +1515 -0
  58. data/lib/unmagic/components/action_view_helpers.rb +558 -3
  59. data/lib/unmagic/components/ai_chat/action_bar.rb +70 -0
  60. data/lib/unmagic/components/ai_chat/attachments.rb +119 -0
  61. data/lib/unmagic/components/ai_chat/branch_picker.rb +65 -0
  62. data/lib/unmagic/components/ai_chat/citation.rb +89 -0
  63. data/lib/unmagic/components/ai_chat/composer.rb +160 -0
  64. data/lib/unmagic/components/ai_chat/failure.rb +80 -0
  65. data/lib/unmagic/components/ai_chat/message.rb +142 -0
  66. data/lib/unmagic/components/ai_chat/payload.rb +93 -0
  67. data/lib/unmagic/components/ai_chat/permission.rb +143 -0
  68. data/lib/unmagic/components/ai_chat/plan.rb +91 -0
  69. data/lib/unmagic/components/ai_chat/proposal.rb +102 -0
  70. data/lib/unmagic/components/ai_chat/reasoning.rb +75 -0
  71. data/lib/unmagic/components/ai_chat/request.rb +206 -0
  72. data/lib/unmagic/components/ai_chat/section.rb +53 -0
  73. data/lib/unmagic/components/ai_chat/slash_menu.rb +68 -0
  74. data/lib/unmagic/components/ai_chat/tool_call.rb +181 -0
  75. data/lib/unmagic/components/ai_chat/transcript.rb +68 -0
  76. data/lib/unmagic/components/ai_chat/welcome.rb +73 -0
  77. data/lib/unmagic/components/ai_chat/workspace.rb +74 -0
  78. data/lib/unmagic/components/ai_chat.rb +31 -0
  79. data/lib/unmagic/components/avatar.rb +134 -0
  80. data/lib/unmagic/components/board.rb +182 -0
  81. data/lib/unmagic/components/browser/app/controllers/unmagic/components/browser/application_controller.rb +80 -0
  82. data/lib/unmagic/components/browser/app/controllers/unmagic/components/browser/demos_controller.rb +137 -0
  83. data/lib/unmagic/components/browser/app/controllers/unmagic/components/browser/pages_controller.rb +40 -0
  84. data/lib/unmagic/components/browser/app/helpers/unmagic/components/browser/application_helper.rb +52 -0
  85. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/board_store.rb +88 -0
  86. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/catalog.rb +65 -0
  87. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/pager.rb +11 -0
  88. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/profile.rb +25 -0
  89. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/reply.rb +72 -0
  90. data/lib/unmagic/components/browser/app/models/unmagic/components/browser/thing.rb +31 -0
  91. data/lib/unmagic/components/browser/app/views/layouts/unmagic/components/browser/application.html.erb +65 -0
  92. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/application/_code.html.erb +7 -0
  93. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/application/_crumb.html.erb +7 -0
  94. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/application/_example.html.erb +37 -0
  95. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/application/_nav.html.erb +17 -0
  96. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/demos/edit_profile.html.erb +9 -0
  97. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat/_conversation.html.erb +101 -0
  98. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat/_live.html.erb +53 -0
  99. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat/_thumbnail.html.erb +7 -0
  100. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat/_welcome.html.erb +12 -0
  101. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_action_bar/_always.html.erb +6 -0
  102. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_action_bar/_hover.html.erb +12 -0
  103. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_action_bar/_thumbnail.html.erb +5 -0
  104. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_attachments/_dropzone.html.erb +17 -0
  105. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_attachments/_sent.html.erb +11 -0
  106. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_attachments/_thumbnail.html.erb +6 -0
  107. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_branch_picker/_positions.html.erb +5 -0
  108. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_branch_picker/_thumbnail.html.erb +3 -0
  109. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_citation/_compact.html.erb +10 -0
  110. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_citation/_quote.html.erb +11 -0
  111. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_citation/_thumbnail.html.erb +7 -0
  112. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_composer/_parts.html.erb +14 -0
  113. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_composer/_states.html.erb +8 -0
  114. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_composer/_thumbnail.html.erb +5 -0
  115. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_failure/_full.html.erb +11 -0
  116. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_failure/_plain.html.erb +3 -0
  117. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_failure/_thumbnail.html.erb +5 -0
  118. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_message/_parts.html.erb +23 -0
  119. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_message/_states.html.erb +9 -0
  120. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_message/_thumbnail.html.erb +4 -0
  121. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_message/_turns.html.erb +16 -0
  122. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_payload/_json.html.erb +4 -0
  123. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_payload/_text.html.erb +4 -0
  124. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_payload/_thumbnail.html.erb +3 -0
  125. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_permission/_answered.html.erb +12 -0
  126. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_permission/_thumbnail.html.erb +7 -0
  127. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_permission/_waiting.html.erb +12 -0
  128. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_plan/_empty.html.erb +3 -0
  129. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_plan/_states.html.erb +9 -0
  130. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_plan/_thumbnail.html.erb +7 -0
  131. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_proposal/_decided.html.erb +11 -0
  132. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_proposal/_inline.html.erb +18 -0
  133. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_proposal/_thumbnail.html.erb +6 -0
  134. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_reasoning/_settled.html.erb +6 -0
  135. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_reasoning/_thinking.html.erb +5 -0
  136. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_reasoning/_thumbnail.html.erb +3 -0
  137. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_request/_answered.html.erb +10 -0
  138. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_request/_choices.html.erb +13 -0
  139. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_request/_form.html.erb +9 -0
  140. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_request/_thumbnail.html.erb +8 -0
  141. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_slash_menu/_composer.html.erb +15 -0
  142. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_slash_menu/_thumbnail.html.erb +9 -0
  143. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_tool_call/_made.html.erb +12 -0
  144. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_tool_call/_run.html.erb +25 -0
  145. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_tool_call/_states.html.erb +22 -0
  146. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_tool_call/_thumbnail.html.erb +7 -0
  147. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_welcome/_suggestions.html.erb +11 -0
  148. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_welcome/_thumbnail.html.erb +6 -0
  149. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_workspace/_files.html.erb +12 -0
  150. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/ai_chat_workspace/_thumbnail.html.erb +7 -0
  151. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/avatar/_group.html.erb +5 -0
  152. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/avatar/_sizes.html.erb +8 -0
  153. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/avatar/_thumbnail.html.erb +5 -0
  154. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/avatar/_tints.html.erb +7 -0
  155. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/badge/_in_context.html.erb +14 -0
  156. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/badge/_thumbnail.html.erb +5 -0
  157. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/badge/_tones.html.erb +8 -0
  158. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/board/_handles.html.erb +13 -0
  159. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/board/_live.html.erb +34 -0
  160. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/board/_thumbnail.html.erb +10 -0
  161. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/button/_sizes.html.erb +12 -0
  162. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/button/_thumbnail.html.erb +4 -0
  163. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/button/_variants.html.erb +6 -0
  164. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/callout/_neutral.html.erb +3 -0
  165. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/callout/_thumbnail.html.erb +5 -0
  166. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/callout/_tones.html.erb +14 -0
  167. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/card/_actions_and_footer.html.erb +7 -0
  168. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/card/_flush_table.html.erb +6 -0
  169. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/card/_link.html.erb +6 -0
  170. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/card/_thumbnail.html.erb +6 -0
  171. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/confirm/_danger.html.erb +3 -0
  172. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/confirm/_plain.html.erb +2 -0
  173. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/confirm/_thumbnail.html.erb +7 -0
  174. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/copy_button/_from_element.html.erb +4 -0
  175. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/copy_button/_thumbnail.html.erb +4 -0
  176. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/copy_button/_with_label.html.erb +3 -0
  177. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/detail_list/_inline.html.erb +5 -0
  178. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/detail_list/_stacked.html.erb +7 -0
  179. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/detail_list/_thumbnail.html.erb +5 -0
  180. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/dialog/_dialog_tag.html.erb +8 -0
  181. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/dialog/_modal.html.erb +10 -0
  182. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/dialog/_thumbnail.html.erb +4 -0
  183. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/elapsed/_countdown.html.erb +3 -0
  184. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/elapsed/_running.html.erb +8 -0
  185. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/elapsed/_thumbnail.html.erb +4 -0
  186. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/empty_state/_default.html.erb +1 -0
  187. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/empty_state/_thumbnail.html.erb +3 -0
  188. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_autogrow.html.erb +4 -0
  189. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_control_classes.html.erb +54 -0
  190. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_fields.html.erb +28 -0
  191. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_states.html.erb +11 -0
  192. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_thumbnail.html.erb +8 -0
  193. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/forms/_uuid_field.html.erb +9 -0
  194. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/local_time/_absolute.html.erb +5 -0
  195. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/local_time/_relative.html.erb +14 -0
  196. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/local_time/_thumbnail.html.erb +14 -0
  197. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/menu/_actions.html.erb +10 -0
  198. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/menu/_labelled.html.erb +6 -0
  199. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/menu/_thumbnail.html.erb +14 -0
  200. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/page_header/_default.html.erb +7 -0
  201. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/page_header/_thumbnail.html.erb +5 -0
  202. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/page_header/_with_leading.html.erb +14 -0
  203. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_blocking_out.html.erb +25 -0
  204. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_card.html.erb +7 -0
  205. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_detail_list.html.erb +15 -0
  206. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_page_header.html.erb +7 -0
  207. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_text_and_button.html.erb +17 -0
  208. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/skeleton/_thumbnail.html.erb +10 -0
  209. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/sortable_list/_connected.html.erb +17 -0
  210. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/sortable_list/_grid.html.erb +20 -0
  211. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/sortable_list/_handles.html.erb +22 -0
  212. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/sortable_list/_thumbnail.html.erb +7 -0
  213. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/streaming_markdown/_reveal.html.erb +19 -0
  214. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/streaming_markdown/_stopped.html.erb +31 -0
  215. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/streaming_markdown/_thumbnail.html.erb +5 -0
  216. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/table/_deferred.html.erb +6 -0
  217. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/table/_empty.html.erb +5 -0
  218. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/table/_sortable.html.erb +8 -0
  219. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/table/_table_tag.html.erb +4 -0
  220. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/table/_thumbnail.html.erb +4 -0
  221. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tabs/_links.html.erb +5 -0
  222. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tabs/_panels.html.erb +18 -0
  223. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tabs/_thumbnail.html.erb +5 -0
  224. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/toast/_above_dialog.html.erb +10 -0
  225. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/toast/_flash.html.erb +13 -0
  226. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/toast/_stream.html.erb +6 -0
  227. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/toast/_thumbnail.html.erb +10 -0
  228. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tooltip/_placement.html.erb +8 -0
  229. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tooltip/_term.html.erb +5 -0
  230. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/examples/tooltip/_thumbnail.html.erb +5 -0
  231. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/pages/component.html.erb +58 -0
  232. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/pages/installation.html.erb +47 -0
  233. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/pages/overview.html.erb +49 -0
  234. data/lib/unmagic/components/browser/app/views/unmagic/components/browser/pages/theming.html.erb +52 -0
  235. data/lib/unmagic/components/browser/assets/browser.css +2 -0
  236. data/lib/unmagic/components/browser/catalog/ai_chat.rb +21 -0
  237. data/lib/unmagic/components/browser/catalog/ai_chat_action_bar.rb +15 -0
  238. data/lib/unmagic/components/browser/catalog/ai_chat_attachments.rb +15 -0
  239. data/lib/unmagic/components/browser/catalog/ai_chat_branch_picker.rb +12 -0
  240. data/lib/unmagic/components/browser/catalog/ai_chat_citation.rb +15 -0
  241. data/lib/unmagic/components/browser/catalog/ai_chat_composer.rb +16 -0
  242. data/lib/unmagic/components/browser/catalog/ai_chat_failure.rb +15 -0
  243. data/lib/unmagic/components/browser/catalog/ai_chat_message.rb +17 -0
  244. data/lib/unmagic/components/browser/catalog/ai_chat_payload.rb +16 -0
  245. data/lib/unmagic/components/browser/catalog/ai_chat_permission.rb +15 -0
  246. data/lib/unmagic/components/browser/catalog/ai_chat_plan.rb +14 -0
  247. data/lib/unmagic/components/browser/catalog/ai_chat_proposal.rb +15 -0
  248. data/lib/unmagic/components/browser/catalog/ai_chat_reasoning.rb +14 -0
  249. data/lib/unmagic/components/browser/catalog/ai_chat_request.rb +17 -0
  250. data/lib/unmagic/components/browser/catalog/ai_chat_slash_menu.rb +14 -0
  251. data/lib/unmagic/components/browser/catalog/ai_chat_tool_call.rb +17 -0
  252. data/lib/unmagic/components/browser/catalog/ai_chat_welcome.rb +13 -0
  253. data/lib/unmagic/components/browser/catalog/ai_chat_workspace.rb +12 -0
  254. data/lib/unmagic/components/browser/catalog/avatar.rb +15 -0
  255. data/lib/unmagic/components/browser/catalog/badge.rb +11 -0
  256. data/lib/unmagic/components/browser/catalog/board.rb +18 -0
  257. data/lib/unmagic/components/browser/catalog/button.rb +14 -0
  258. data/lib/unmagic/components/browser/catalog/callout.rb +13 -0
  259. data/lib/unmagic/components/browser/catalog/card.rb +16 -0
  260. data/lib/unmagic/components/browser/catalog/confirm.rb +14 -0
  261. data/lib/unmagic/components/browser/catalog/copy_button.rb +15 -0
  262. data/lib/unmagic/components/browser/catalog/detail_list.rb +14 -0
  263. data/lib/unmagic/components/browser/catalog/dialog.rb +15 -0
  264. data/lib/unmagic/components/browser/catalog/elapsed.rb +14 -0
  265. data/lib/unmagic/components/browser/catalog/empty_state.rb +11 -0
  266. data/lib/unmagic/components/browser/catalog/forms.rb +21 -0
  267. data/lib/unmagic/components/browser/catalog/local_time.rb +13 -0
  268. data/lib/unmagic/components/browser/catalog/menu.rb +15 -0
  269. data/lib/unmagic/components/browser/catalog/page_header.rb +13 -0
  270. data/lib/unmagic/components/browser/catalog/skeleton.rb +18 -0
  271. data/lib/unmagic/components/browser/catalog/sortable_list.rb +17 -0
  272. data/lib/unmagic/components/browser/catalog/streaming_markdown.rb +17 -0
  273. data/lib/unmagic/components/browser/catalog/table.rb +17 -0
  274. data/lib/unmagic/components/browser/catalog/tabs.rb +15 -0
  275. data/lib/unmagic/components/browser/catalog/toast.rb +17 -0
  276. data/lib/unmagic/components/browser/catalog/tooltip.rb +14 -0
  277. data/lib/unmagic/components/browser/config/routes.rb +41 -0
  278. data/lib/unmagic/components/browser/tailwind/browser.css +33 -0
  279. data/lib/unmagic/components/browser.rb +39 -0
  280. data/lib/unmagic/components/configuration.rb +31 -1
  281. data/lib/unmagic/components/duration.rb +35 -0
  282. data/lib/unmagic/components/elapsed.rb +46 -0
  283. data/lib/unmagic/components/engine.rb +9 -0
  284. data/lib/unmagic/components/icons.rb +34 -26
  285. data/lib/unmagic/components/modal.rb +1 -1
  286. data/lib/unmagic/components/page_header.rb +8 -0
  287. data/lib/unmagic/components/sortable.rb +104 -0
  288. data/lib/unmagic/components/streaming_markdown.rb +34 -0
  289. data/lib/unmagic/components/turbo_stream_actions.rb +16 -0
  290. data/lib/unmagic/components/version.rb +1 -1
  291. data/lib/unmagic/components.rb +30 -1
  292. metadata +308 -4
@@ -0,0 +1,269 @@
1
+ // <unmagic-streaming-markdown id="…_content"> — server-rendered HTML revealed at a
2
+ // steady pace as fuller renders arrive, rendered by `streaming_markdown_tag` and
3
+ // `ai_chat_message`.
4
+ //
5
+ // The server renders a reply to HTML and sends the whole render on every flush,
6
+ // through the `stream_markdown` Turbo Stream action registered below. Flushes
7
+ // arrive in bursts, which paints chunkily; this element decouples reveal cadence
8
+ // from arrival. It holds the newest render as a target and eases a character
9
+ // cursor toward it every animation frame. Parsing stays on the server: this only
10
+ // reveals a growing prefix of the HTML.
11
+ //
12
+ // It reveals at a constant velocity — the model's own average production rate —
13
+ // and lets the buffer (how far the cursor trails the live edge) absorb the bursts.
14
+ // A proportional "close a fraction of the gap" scheme would speed up and slow down
15
+ // on every burst. The rate is an average over RATE_SMOOTHING; a soft pull toward a
16
+ // small buffer (BUFFER_SECONDS) stops the trailing distance drifting; a lag cap
17
+ // catches up on a big jump (a reconnect) and a trickle floor finishes the tail.
18
+ //
19
+ // The visible prefix is reconciled block by block: settled leading blocks
20
+ // (identical by tag and text) are left alone, and everything from the first
21
+ // changed block is rebuilt, which keeps lists and tables structurally intact.
22
+ //
23
+ // When the settled turn replaces the streaming one mid-reveal, the replacement
24
+ // picks up where the old element left off (HANDOFF) and paces to the end, but only
25
+ // if its content extends what was on screen; a divergent settle (a "stopped" note)
26
+ // shows at once.
27
+ //
28
+ // final settled: show the content as it is and refuse any later flush.
29
+ // This is what lets a Stop button's optimistic swap stick before
30
+ // the server has settled the turn.
31
+ // aria-busy rendered while a reply is live; removed once the reveal has
32
+ // caught up and gone quiet, so a live region announces the reply
33
+ // once rather than on every flush.
34
+ //
35
+ // It fires unmagic-streaming-markdown:settle when it catches up. Under reduced
36
+ // motion every flush paints in full. Needs Turbo.
37
+ import { Turbo } from "@hotwired/turbo-rails"
38
+
39
+ const RATE_SMOOTHING = 0.6 // seconds: the window for the production-rate average, which is the reveal speed
40
+ const BUFFER_SECONDS = 0.3 // seconds of text the cursor aims to keep between itself and the live edge
41
+ const SOFTNESS = 0.4 // per second: how firmly that buffer is held; smaller is steadier and drifts more
42
+ const MAX_LAG_SECONDS = 1.5 // always drain the whole backlog within this long
43
+ const MIN_TRICKLE = 24 // characters a second, so the tail still finishes
44
+ const IDLE_MS = 700 // keep the loop alive this long after the last flush
45
+ const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)")
46
+
47
+ // Reveal progress carried across an element swap, keyed by element id.
48
+ const HANDOFF = new Map()
49
+
50
+ class UnmagicStreamingMarkdown extends HTMLElement {
51
+ #target = null // DocumentFragment: the newest full render
52
+ #targetLength = 0 // its revealable length (the sum of top-level block text)
53
+ #revealed = 0 // characters on screen, fractional between frames
54
+ #rate = 0 // estimated production rate in characters a second
55
+ #lastArrival = 0
56
+ #lastFrame = 0
57
+ #frame = null
58
+ #frozen = false
59
+
60
+ connectedCallback() {
61
+ if (this.hasAttribute("final")) {
62
+ HANDOFF.delete(this.id)
63
+ this.#frozen = true
64
+ this.#revealed = blockLength(this)
65
+ return
66
+ }
67
+
68
+ const handoff = HANDOFF.get(this.id)
69
+ HANDOFF.delete(this.id)
70
+ const full = blockLength(this)
71
+ const continues =
72
+ handoff && handoff.revealed > 0 && handoff.revealed < full && this.textContent.startsWith(handoff.text)
73
+
74
+ if (continues && !reducedMotion.matches) {
75
+ this.#target = adoptChildren(this)
76
+ this.#targetLength = full
77
+ this.#revealed = handoff.revealed
78
+ this.#rate = handoff.rate
79
+ this.#lastArrival = performance.now()
80
+ this.#paint()
81
+ this.#schedule()
82
+ } else {
83
+ this.#revealed = full
84
+ this.#targetLength = full
85
+ }
86
+ }
87
+
88
+ disconnectedCallback() {
89
+ if (this.#frame) cancelAnimationFrame(this.#frame)
90
+ this.#frame = null
91
+
92
+ if (this.#revealed < this.#targetLength) {
93
+ HANDOFF.set(this.id, { revealed: this.#revealed, rate: this.#rate, text: this.textContent })
94
+ } else {
95
+ HANDOFF.delete(this.id)
96
+ }
97
+ }
98
+
99
+ // Called by the stream_markdown action with a fragment of the whole render so far.
100
+ streamUpdate(fragment) {
101
+ if (this.#frozen) return
102
+
103
+ const now = performance.now()
104
+ const length = blockLength(fragment)
105
+ const grew = Math.max(0, length - this.#targetLength)
106
+ this.#target = fragment
107
+ this.#targetLength = length
108
+ if (this.#revealed > length) this.#revealed = length
109
+
110
+ this.#observeRate(now, grew)
111
+ this.#lastArrival = now
112
+ this.setAttribute("aria-busy", "true")
113
+
114
+ if (reducedMotion.matches) {
115
+ this.#revealed = length
116
+ this.#paint()
117
+ this.#settle()
118
+ } else {
119
+ this.#paint()
120
+ this.#schedule()
121
+ }
122
+ }
123
+
124
+ // Lock onto the first reading, so the opening characters don't crawl while the
125
+ // average warms up.
126
+ #observeRate(now, grew) {
127
+ if (!this.#lastArrival) return
128
+
129
+ const dt = (now - this.#lastArrival) / 1000
130
+ if (dt <= 0) return
131
+
132
+ const instant = grew / dt
133
+ if (this.#rate === 0) this.#rate = instant
134
+ else this.#rate += (1 - Math.exp(-dt / RATE_SMOOTHING)) * (instant - this.#rate)
135
+ }
136
+
137
+ #schedule() {
138
+ if (this.#frame) return
139
+ this.#lastFrame = performance.now()
140
+ this.#frame = requestAnimationFrame((now) => this.#tick(now))
141
+ }
142
+
143
+ #tick(now) {
144
+ this.#frame = null
145
+ const dt = Math.min(0.05, (now - this.#lastFrame) / 1000) // a backgrounded tab resumes gently
146
+ this.#lastFrame = now
147
+
148
+ const backlog = this.#targetLength - this.#revealed
149
+ if (backlog > 0) {
150
+ let velocity = this.#rate + (backlog - this.#rate * BUFFER_SECONDS) * SOFTNESS
151
+ velocity = Math.max(velocity, backlog / MAX_LAG_SECONDS)
152
+ velocity = Math.min(2000, Math.max(MIN_TRICKLE, velocity))
153
+ this.#revealed = Math.min(this.#targetLength, this.#revealed + velocity * dt)
154
+ if (this.#targetLength - this.#revealed < 0.5) this.#revealed = this.#targetLength
155
+ this.#paint()
156
+ }
157
+
158
+ const streaming = now - this.#lastArrival < IDLE_MS
159
+ if (this.#revealed < this.#targetLength || streaming) this.#schedule()
160
+ else this.#settle()
161
+ }
162
+
163
+ #settle() {
164
+ if (!this.hasAttribute("aria-busy")) return
165
+ this.removeAttribute("aria-busy")
166
+ this.dispatchEvent(new CustomEvent("unmagic-streaming-markdown:settle", { bubbles: true }))
167
+ }
168
+
169
+ #paint() {
170
+ const visible = truncatedClone(this.#target, Math.floor(this.#revealed))
171
+ try {
172
+ this.#reconcile(visible)
173
+ } catch {
174
+ this.replaceChildren(visible)
175
+ }
176
+ }
177
+
178
+ #reconcile(fragment) {
179
+ const incoming = Array.from(fragment.children)
180
+ if (incoming.length === 0) return this.replaceChildren(fragment)
181
+
182
+ const current = Array.from(this.children)
183
+ let i = 0
184
+ while (
185
+ i < current.length &&
186
+ i < incoming.length &&
187
+ current[i].nodeName === incoming[i].nodeName &&
188
+ current[i].textContent === incoming[i].textContent
189
+ )
190
+ i++
191
+
192
+ if (i === 0) {
193
+ this.replaceChildren()
194
+ } else {
195
+ const anchor = current[i - 1]
196
+ while (anchor.nextSibling) anchor.nextSibling.remove()
197
+ }
198
+ for (let k = i; k < incoming.length; k++) this.append(incoming[k])
199
+ }
200
+ }
201
+
202
+ customElements.get("unmagic-streaming-markdown") ||
203
+ customElements.define("unmagic-streaming-markdown", UnmagicStreamingMarkdown)
204
+
205
+ // Move an element's children into a fragment, leaving it empty to reveal into.
206
+ function adoptChildren(root) {
207
+ const fragment = document.createDocumentFragment()
208
+ while (root.firstChild) fragment.append(root.firstChild)
209
+ return fragment
210
+ }
211
+
212
+ // A clone of the target's top-level blocks, cut to `limit` characters of block
213
+ // text; the last block is split where the cursor lands inside it.
214
+ function truncatedClone(target, limit) {
215
+ const fragment = document.createDocumentFragment()
216
+ if (!target) return fragment
217
+
218
+ let count = 0
219
+ for (const child of target.children) {
220
+ if (count >= limit) break
221
+ const length = child.textContent.length
222
+ const clone = child.cloneNode(true)
223
+ if (count + length <= limit) {
224
+ fragment.append(clone)
225
+ count += length
226
+ } else {
227
+ truncateTo(clone, limit - count)
228
+ fragment.append(clone)
229
+ break
230
+ }
231
+ }
232
+ return fragment
233
+ }
234
+
235
+ // The text length of an element's top-level blocks. Whitespace between blocks
236
+ // doesn't count.
237
+ function blockLength(root) {
238
+ let length = 0
239
+ for (const child of root.children) length += child.textContent.length
240
+ return length
241
+ }
242
+
243
+ function truncateTo(root, limit) {
244
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
245
+ let count = 0
246
+ let lastKept = null
247
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
248
+ const length = node.data.length
249
+ if (count + length <= limit) {
250
+ count += length
251
+ lastKept = node
252
+ if (count === limit) break
253
+ } else {
254
+ node.data = node.data.slice(0, limit - count)
255
+ lastKept = node
256
+ break
257
+ }
258
+ }
259
+ if (!lastKept) return root.replaceChildren()
260
+ for (let node = lastKept; node && node !== root; node = node.parentNode) {
261
+ while (node.nextSibling) node.nextSibling.remove()
262
+ }
263
+ }
264
+
265
+ // `update`, for streamed Markdown: hand the whole render to the element, which
266
+ // paces its way to it.
267
+ Turbo.StreamActions.stream_markdown = function () {
268
+ for (const element of this.targetElements) element.streamUpdate?.(this.templateContent)
269
+ }
@@ -3,8 +3,8 @@
3
3
  // Each toast arrives as an inert <template data-unmagic-toast-template> inside the
4
4
  // element: rendered with the page, morphed in by a refresh, or appended by
5
5
  // turbo_stream.toast. The element clones each one into its stack, animates it in,
6
- // and dismisses it after the element's `duration` (milliseconds). Hovering or
7
- // focusing a toast holds it open.
6
+ // and dismisses it after the element's `duration` (milliseconds), or keeps it up
7
+ // until it is dismissed when that is 0. Hovering or focusing a toast holds it open.
8
8
  //
9
9
  // The stack is data-turbo-permanent, so a toast on screen survives a Drive visit
10
10
  // or a morph. Its timer lives here, in the module, keyed by the toast rather than
@@ -54,9 +54,16 @@ class UnmagicToasts extends HTMLElement {
54
54
  return this.querySelector(":scope > .UnmagicToasts__stack")
55
55
  }
56
56
 
57
+ // `duration="0"` keeps a toast up until it is dismissed. Absent, empty, negative or unparseable
58
+ // all fall back to the default rather than silently pinning every toast to the screen — which is
59
+ // why this isn't `Number(attr) || DURATION`: Number(null) and Number("") are both 0, so a missing
60
+ // attribute would have read as "never" too.
57
61
  get duration() {
58
- const duration = Number(this.getAttribute("duration"))
59
- return duration > 0 ? duration : DURATION
62
+ const raw = this.getAttribute("duration")?.trim()
63
+ if (!raw) return DURATION
64
+
65
+ const duration = Number(raw)
66
+ return Number.isFinite(duration) && duration >= 0 ? duration : DURATION
60
67
  }
61
68
 
62
69
  #consume() {
@@ -118,6 +125,9 @@ class UnmagicToasts extends HTMLElement {
118
125
 
119
126
  function schedule(toast, duration) {
120
127
  if (timers.has(toast)) return
128
+ // A duration of 0 is a toast that waits to be dismissed: no timer, and so nothing for hold and
129
+ // release to pause — both look the toast up in `timers` and leave when it isn't there.
130
+ if (duration === 0) return
121
131
 
122
132
  const entry = { remaining: duration, started: 0, timer: null }
123
133
  timers.set(toast, entry)
@@ -0,0 +1,76 @@
1
+ // <unmagic-toolbar role="toolbar"> — a row of controls with one Tab stop,
2
+ // rendered by `ai_chat_action_bar`.
3
+ //
4
+ // The WAI-ARIA toolbar pattern: Tab enters and leaves the row as one stop, and
5
+ // Left, Right, Home and End move between its controls (a roving tabindex). The
6
+ // control that last had focus is the one Tab returns to. Controls are its
7
+ // buttons, links and anything already carrying a tabindex, skipping disabled and
8
+ // hidden ones. It rescans when its contents change, so a streamed or
9
+ // re-rendered bar needs no setup.
10
+
11
+ class UnmagicToolbar extends HTMLElement {
12
+ #observer = new MutationObserver(() => this.#reset())
13
+
14
+ constructor() {
15
+ super()
16
+ this.addEventListener("keydown", this.#keydown)
17
+ this.addEventListener("focusin", (event) => {
18
+ if (this.#controls.includes(event.target)) this.#activate(event.target)
19
+ })
20
+ }
21
+
22
+ connectedCallback() {
23
+ this.#reset()
24
+ this.#observer.observe(this, { childList: true, subtree: true })
25
+ }
26
+
27
+ disconnectedCallback() {
28
+ this.#observer.disconnect()
29
+ }
30
+
31
+ get #controls() {
32
+ return Array.from(this.querySelectorAll("button, a[href], [tabindex]")).filter(
33
+ (control) => !control.disabled && control.getAttribute("aria-disabled") !== "true" && !control.closest("[hidden]"),
34
+ )
35
+ }
36
+
37
+ #reset() {
38
+ const controls = this.#controls
39
+ const current = controls.find((control) => control.getAttribute("tabindex") === "0") ?? controls[0]
40
+ if (current) this.#activate(current, controls)
41
+ }
42
+
43
+ #activate(target, controls = this.#controls) {
44
+ for (const control of controls) control.setAttribute("tabindex", control === target ? "0" : "-1")
45
+ }
46
+
47
+ #keydown = (event) => {
48
+ const controls = this.#controls
49
+ const index = controls.indexOf(document.activeElement)
50
+ if (index === -1) return
51
+
52
+ let next
53
+ switch (event.key) {
54
+ case "ArrowRight":
55
+ next = controls[(index + 1) % controls.length]
56
+ break
57
+ case "ArrowLeft":
58
+ next = controls[(index - 1 + controls.length) % controls.length]
59
+ break
60
+ case "Home":
61
+ next = controls[0]
62
+ break
63
+ case "End":
64
+ next = controls[controls.length - 1]
65
+ break
66
+ default:
67
+ return
68
+ }
69
+
70
+ event.preventDefault()
71
+ this.#activate(next, controls)
72
+ next.focus()
73
+ }
74
+ }
75
+
76
+ customElements.get("unmagic-toolbar") || customElements.define("unmagic-toolbar", UnmagicToolbar)
@@ -9,7 +9,8 @@
9
9
  // server is now confirming — it is replaced in place, so a record that is both on the page and
10
10
  // broadcast never duplicates. Otherwise it is inserted into the target container at the position
11
11
  // its id sorts to, so out-of-order delivery still lands in id order. That ordering assumes
12
- // time-ordered ids (UUIDv7, ULID); with random ids the position is stable but arbitrary.
12
+ // time-ordered ids (UUIDv7, ULID); with random ids the position is stable but arbitrary. The rule
13
+ // itself lives in placement.js, shared with <unmagic-optimistic>.
13
14
  //
14
15
  // A list showing newest first says so, and the comparison flips:
15
16
  //
@@ -18,28 +19,11 @@
18
19
  // broadcast_action_to "tasks", action: :upsert, target: "task_rows",
19
20
  // attributes: { order: "desc" }, partial: "tasks/row", locals: { task: self }
20
21
  import { Turbo } from "@hotwired/turbo-rails"
22
+ import { place } from "unmagic/components/placement"
21
23
 
22
24
  Turbo.StreamActions.upsert = function () {
23
25
  const incoming = this.templateContent.firstElementChild
24
26
  if (!incoming) return
25
27
 
26
- const existing = incoming.id ? document.getElementById(incoming.id) : null
27
- if (existing) {
28
- existing.replaceWith(incoming)
29
- return
30
- }
31
-
32
- const container = this.targetElements[0]
33
- if (!container) return
34
-
35
- const descending = this.getAttribute("order") === "desc"
36
-
37
- // Without an id there is nothing to sort on, so it goes last.
38
- const successor = incoming.id
39
- ? Array.from(container.children).find(
40
- (child) => child.id && (descending ? child.id < incoming.id : child.id > incoming.id),
41
- )
42
- : null
43
-
44
- container.insertBefore(incoming, successor ?? null)
28
+ place(incoming, this.targetElements[0], { descending: this.getAttribute("order") === "desc" })
45
29
  }
@@ -17,3 +17,13 @@ import "unmagic/components/tabs"
17
17
  import "unmagic/components/clipboard"
18
18
  import "unmagic/components/autogrow"
19
19
  import "unmagic/components/uuid_input"
20
+ import "unmagic/components/elapsed"
21
+ import "unmagic/components/autoscroll"
22
+ import "unmagic/components/optimistic"
23
+ import "unmagic/components/streaming_markdown"
24
+ import "unmagic/components/toolbar"
25
+ import "unmagic/components/slash_menu"
26
+ import "unmagic/components/dropzone"
27
+ import "unmagic/components/ai_chat"
28
+ import "unmagic/components/sortable"
29
+ import "unmagic/components/board"