panda-core 0.12.2 → 1.1.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 (255) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +53 -0
  4. data/app/assets/builds/panda_core_admin.css +2 -0
  5. data/app/assets/tailwind/application.css +413 -131
  6. data/app/builders/panda/core/form_builder.rb +129 -27
  7. data/app/components/panda/core/UI/badge.html.erb +10 -0
  8. data/app/components/panda/core/UI/badge.rb +14 -30
  9. data/app/components/panda/core/UI/button.html.erb +5 -0
  10. data/app/components/panda/core/UI/button.rb +15 -19
  11. data/app/components/panda/core/UI/card.html.erb +3 -0
  12. data/app/components/panda/core/UI/card.rb +7 -6
  13. data/app/components/panda/core/admin/attribute_diff_component.html.erb +21 -0
  14. data/app/components/panda/core/admin/attribute_diff_component.rb +29 -0
  15. data/app/components/panda/core/admin/breadcrumb_component.html.erb +36 -0
  16. data/app/components/panda/core/admin/breadcrumb_component.rb +12 -64
  17. data/app/components/panda/core/admin/breadcrumbs_component.html.erb +16 -0
  18. data/app/components/panda/core/admin/breadcrumbs_component.rb +26 -0
  19. data/app/components/panda/core/admin/button_component.html.erb +9 -0
  20. data/app/components/panda/core/admin/button_component.rb +37 -32
  21. data/app/components/panda/core/admin/callout_component.html.erb +17 -0
  22. data/app/components/panda/core/admin/callout_component.rb +57 -0
  23. data/app/components/panda/core/admin/code_block_component.html.erb +9 -0
  24. data/app/components/panda/core/admin/code_block_component.rb +23 -0
  25. data/app/components/panda/core/admin/container_component.html.erb +27 -0
  26. data/app/components/panda/core/admin/container_component.rb +11 -77
  27. data/app/components/panda/core/admin/contributors_summary_component.html.erb +15 -0
  28. data/app/components/panda/core/admin/contributors_summary_component.rb +23 -0
  29. data/app/components/panda/core/admin/dashboard_default_content_component.html.erb +70 -0
  30. data/app/components/panda/core/admin/dashboard_default_content_component.rb +32 -0
  31. data/app/components/panda/core/admin/delete_button_component.html.erb +4 -0
  32. data/app/components/panda/core/admin/delete_button_component.rb +41 -0
  33. data/app/components/panda/core/admin/dropdown_button_component.html.erb +28 -0
  34. data/app/components/panda/core/admin/dropdown_button_component.rb +100 -0
  35. data/app/components/panda/core/admin/dropdown_component.html.erb +32 -0
  36. data/app/components/panda/core/admin/dropdown_component.rb +65 -0
  37. data/app/components/panda/core/admin/empty_state_component.html.erb +16 -0
  38. data/app/components/panda/core/admin/empty_state_component.rb +25 -0
  39. data/app/components/panda/core/admin/file_gallery_component.html.erb +58 -0
  40. data/app/components/panda/core/admin/file_gallery_component.rb +26 -98
  41. data/app/components/panda/core/admin/filter_pills_component.html.erb +6 -0
  42. data/app/components/panda/core/admin/filter_pills_component.rb +29 -0
  43. data/app/components/panda/core/admin/flash_message_component.html.erb +11 -0
  44. data/app/components/panda/core/admin/flash_message_component.rb +33 -60
  45. data/app/components/panda/core/admin/form_error_component.html.erb +7 -0
  46. data/app/components/panda/core/admin/form_error_component.rb +8 -16
  47. data/app/components/panda/core/admin/form_footer_component.html.erb +16 -0
  48. data/app/components/panda/core/admin/form_footer_component.rb +65 -0
  49. data/app/components/panda/core/admin/form_input_component.html.erb +1 -0
  50. data/app/components/panda/core/admin/form_input_component.rb +21 -13
  51. data/app/components/panda/core/admin/form_section_component.html.erb +16 -0
  52. data/app/components/panda/core/admin/form_section_component.rb +64 -0
  53. data/app/components/panda/core/admin/form_select_component.html.erb +5 -0
  54. data/app/components/panda/core/admin/form_select_component.rb +25 -26
  55. data/app/components/panda/core/admin/heading_component.html.erb +26 -0
  56. data/app/components/panda/core/admin/heading_component.rb +23 -60
  57. data/app/components/panda/core/admin/login_form_component.html.erb +38 -0
  58. data/app/components/panda/core/admin/login_form_component.rb +28 -0
  59. data/app/components/panda/core/admin/main_layout_component.html.erb +88 -0
  60. data/app/components/panda/core/admin/main_layout_component.rb +19 -0
  61. data/app/components/panda/core/admin/my_profile/connected_account_component.html.erb +41 -0
  62. data/app/components/panda/core/admin/my_profile/connected_account_component.rb +27 -0
  63. data/app/components/panda/core/admin/navigation/item_component.html.erb +34 -0
  64. data/app/components/panda/core/admin/navigation/item_component.rb +69 -0
  65. data/app/components/panda/core/admin/navigation/sub_item_component.html.erb +9 -0
  66. data/app/components/panda/core/admin/navigation/sub_item_component.rb +44 -0
  67. data/app/components/panda/core/admin/navigation/user_menu_component.html.erb +30 -0
  68. data/app/components/panda/core/admin/navigation/user_menu_component.rb +36 -0
  69. data/app/components/panda/core/admin/page_header_component.html.erb +28 -0
  70. data/app/components/panda/core/admin/page_header_component.rb +19 -35
  71. data/app/components/panda/core/admin/pagination_component.html.erb +33 -0
  72. data/app/components/panda/core/admin/pagination_component.rb +56 -0
  73. data/app/components/panda/core/admin/panel_component.html.erb +19 -0
  74. data/app/components/panda/core/admin/panel_component.rb +4 -29
  75. data/app/components/panda/core/admin/prose_panel_component.html.erb +6 -0
  76. data/app/components/panda/core/admin/prose_panel_component.rb +43 -0
  77. data/app/components/panda/core/admin/search_bar_component.html.erb +19 -0
  78. data/app/components/panda/core/admin/search_bar_component.rb +31 -0
  79. data/app/components/panda/core/admin/search_filter_bar_component.html.erb +22 -0
  80. data/app/components/panda/core/admin/search_filter_bar_component.rb +27 -0
  81. data/app/components/panda/core/admin/secret_field_component.html.erb +24 -0
  82. data/app/components/panda/core/admin/secret_field_component.rb +31 -0
  83. data/app/components/panda/core/admin/sidebar_component.html.erb +160 -0
  84. data/app/components/panda/core/admin/sidebar_component.rb +55 -0
  85. data/app/components/panda/core/admin/slideover_component.html.erb +67 -0
  86. data/app/components/panda/core/admin/slideover_component.rb +31 -70
  87. data/app/components/panda/core/admin/statistics_component.html.erb +4 -0
  88. data/app/components/panda/core/admin/statistics_component.rb +6 -8
  89. data/app/components/panda/core/admin/tab_bar_component.html.erb +48 -0
  90. data/app/components/panda/core/admin/tab_bar_component.rb +34 -69
  91. data/app/components/panda/core/admin/table_component.html.erb +82 -0
  92. data/app/components/panda/core/admin/table_component.rb +54 -87
  93. data/app/components/panda/core/admin/tag_badge_component.html.erb +7 -0
  94. data/app/components/panda/core/admin/tag_badge_component.rb +21 -0
  95. data/app/components/panda/core/admin/tag_component.html.erb +1 -0
  96. data/app/components/panda/core/admin/tag_component.rb +51 -20
  97. data/app/components/panda/core/admin/tag_input_component.html.erb +37 -0
  98. data/app/components/panda/core/admin/tag_input_component.rb +22 -0
  99. data/app/components/panda/core/admin/tags_panel_component.html.erb +8 -0
  100. data/app/components/panda/core/admin/tags_panel_component.rb +33 -0
  101. data/app/components/panda/core/admin/user_activity_component.html.erb +15 -0
  102. data/app/components/panda/core/admin/user_activity_component.rb +9 -22
  103. data/app/components/panda/core/admin/user_display_component.html.erb +8 -0
  104. data/app/components/panda/core/admin/user_display_component.rb +19 -22
  105. data/app/components/panda/core/base.rb +35 -42
  106. data/app/components/panda/core/shared/footer_component.html.erb +2 -0
  107. data/app/components/panda/core/shared/footer_component.rb +11 -0
  108. data/app/components/panda/core/shared/header_component.html.erb +32 -0
  109. data/app/components/panda/core/shared/header_component.rb +19 -0
  110. data/app/constraints/panda/core/admin_constraint.rb +2 -2
  111. data/app/controllers/concerns/panda/core/admin_authorization.rb +58 -0
  112. data/app/controllers/concerns/panda/core/authorizable.rb +107 -0
  113. data/app/controllers/panda/core/admin/base_controller.rb +62 -3
  114. data/app/controllers/panda/core/admin/editor/search_controller.rb +17 -0
  115. data/app/controllers/panda/core/admin/feature_flags_controller.rb +35 -0
  116. data/app/controllers/panda/core/admin/file_categories_controller.rb +84 -0
  117. data/app/controllers/panda/core/admin/files_controller.rb +230 -0
  118. data/app/controllers/panda/core/admin/import_sessions_controller.rb +120 -0
  119. data/app/controllers/panda/core/admin/my_profile/logins_controller.rb +24 -0
  120. data/app/controllers/panda/core/admin/my_profile_controller.rb +31 -0
  121. data/app/controllers/panda/core/admin/search_controller.rb +15 -0
  122. data/app/controllers/panda/core/admin/sessions_controller.rb +85 -8
  123. data/app/controllers/panda/core/admin/tags_controller.rb +77 -0
  124. data/app/controllers/panda/core/admin/test_sessions_controller.rb +5 -6
  125. data/app/controllers/panda/core/admin/users_controller.rb +184 -0
  126. data/app/controllers/panda/core/application_controller.rb +13 -1
  127. data/app/helpers/panda/core/active_storage_helper.rb +24 -0
  128. data/app/helpers/panda/core/feature_flag_helper.rb +11 -0
  129. data/app/helpers/panda/core/form_helper.rb +15 -0
  130. data/app/javascript/panda/core/controllers/avatar_upload_controller.js +73 -0
  131. data/app/javascript/panda/core/controllers/clipboard_controller.js +119 -0
  132. data/app/javascript/panda/core/controllers/collapsible_item_controller.js +45 -0
  133. data/app/javascript/panda/core/controllers/custom_select_controller.js +256 -0
  134. data/app/javascript/panda/core/controllers/datepicker_controller.js +121 -0
  135. data/app/javascript/panda/core/controllers/daterange_controller.js +193 -0
  136. data/app/javascript/panda/core/controllers/file_gallery_controller.js +134 -0
  137. data/app/javascript/panda/core/controllers/file_upload_controller.js +165 -0
  138. data/app/javascript/panda/core/controllers/global_search_controller.js +184 -0
  139. data/app/javascript/panda/core/controllers/index.js +36 -0
  140. data/app/javascript/panda/core/controllers/mobile_sidebar_controller.js +58 -0
  141. data/app/javascript/panda/core/controllers/navigation_toggle_controller.js +49 -24
  142. data/app/javascript/panda/core/controllers/row_link_controller.js +27 -0
  143. data/app/javascript/panda/core/controllers/tag_input_controller.js +183 -0
  144. data/app/javascript/panda/core/tailwindcss-stimulus-components.js +2 -3
  145. data/app/javascript/panda/core/vendor/@fortawesome--fontawesome-free@7.2.0.js +8129 -0
  146. data/app/javascript/panda/core/vendor/@hotwired--turbo.js +7197 -100
  147. data/app/javascript/panda/core/vendor/@rails--actioncable--src.js +511 -2
  148. data/app/javascript/panda/core/vendor/@tailwindplus--elements@1.0.22.js +65 -0
  149. data/app/javascript/panda/core/vendor/cropperjs@2.1.0.js +3 -0
  150. data/app/javascript/panda/core/vendor/vanilla-calendar-pro.js +2 -0
  151. data/app/models/concerns/panda/core/has_metadata.rb +164 -0
  152. data/app/models/concerns/panda/core/importable.rb +79 -0
  153. data/app/models/concerns/panda/core/searchable.rb +66 -0
  154. data/app/models/concerns/panda/core/taggable.rb +37 -0
  155. data/app/models/panda/core/feature_flag.rb +56 -0
  156. data/app/models/panda/core/file_categorization.rb +14 -0
  157. data/app/models/panda/core/file_category.rb +45 -0
  158. data/app/models/panda/core/import_session.rb +79 -0
  159. data/app/models/panda/core/presence.rb +24 -0
  160. data/app/models/panda/core/tag.rb +33 -0
  161. data/app/models/panda/core/tagging.rb +14 -0
  162. data/app/models/panda/core/user.rb +96 -18
  163. data/app/models/panda/core/user_activity.rb +42 -0
  164. data/app/models/panda/core/user_session.rb +35 -0
  165. data/app/services/panda/core/attach_avatar_service.rb +15 -3
  166. data/app/services/panda/core/csv_import_service.rb +65 -0
  167. data/app/services/panda/core/file_categorizer.rb +52 -0
  168. data/app/services/panda/core/file_parser.rb +76 -0
  169. data/app/services/panda/core/invite_user_service.rb +44 -0
  170. data/app/services/panda/core/presence_service.rb +49 -0
  171. data/app/views/layouts/panda/core/admin.html.erb +6 -67
  172. data/app/views/layouts/panda/core/admin_simple.html.erb +2 -2
  173. data/app/views/panda/core/admin/dashboard/_default_content.html.erb +1 -73
  174. data/app/views/panda/core/admin/dashboard/show.html.erb +12 -15
  175. data/app/views/panda/core/admin/feature_flags/index.html.erb +56 -0
  176. data/app/views/panda/core/admin/file_categories/_form.html.erb +20 -0
  177. data/app/views/panda/core/admin/file_categories/edit.html.erb +5 -0
  178. data/app/views/panda/core/admin/file_categories/index.html.erb +55 -0
  179. data/app/views/panda/core/admin/file_categories/new.html.erb +5 -0
  180. data/app/views/panda/core/admin/files/_file_details.html.erb +156 -0
  181. data/app/views/panda/core/admin/files/index.html.erb +61 -0
  182. data/app/views/panda/core/admin/import_sessions/column_map.html.erb +43 -0
  183. data/app/views/panda/core/admin/import_sessions/index.html.erb +48 -0
  184. data/app/views/panda/core/admin/import_sessions/new.html.erb +37 -0
  185. data/app/views/panda/core/admin/import_sessions/preview.html.erb +51 -0
  186. data/app/views/panda/core/admin/import_sessions/show.html.erb +87 -0
  187. data/app/views/panda/core/admin/my_profile/edit.html.erb +51 -39
  188. data/app/views/panda/core/admin/my_profile/logins/show.html.erb +72 -0
  189. data/app/views/panda/core/admin/sessions/new.html.erb +1 -38
  190. data/app/views/panda/core/admin/shared/_breadcrumbs.html.erb +1 -18
  191. data/app/views/panda/core/admin/shared/_sidebar.html.erb +1 -99
  192. data/app/views/panda/core/admin/shared/_slideover.html.erb +3 -3
  193. data/app/views/panda/core/admin/tags/_form.html.erb +21 -0
  194. data/app/views/panda/core/admin/tags/edit.html.erb +4 -0
  195. data/app/views/panda/core/admin/tags/index.html.erb +41 -0
  196. data/app/views/panda/core/admin/tags/new.html.erb +4 -0
  197. data/app/views/panda/core/admin/users/activity.html.erb +90 -0
  198. data/app/views/panda/core/admin/users/edit.html.erb +48 -0
  199. data/app/views/panda/core/admin/users/index.html.erb +161 -0
  200. data/app/views/panda/core/admin/users/sessions.html.erb +103 -0
  201. data/app/views/panda/core/admin/users/show.html.erb +224 -0
  202. data/app/views/panda/core/shared/_footer.html.erb +1 -2
  203. data/app/views/panda/core/shared/_header.html.erb +4 -22
  204. data/config/brakeman.ignore +68 -31
  205. data/config/importmap.rb +13 -11
  206. data/config/routes.rb +49 -2
  207. data/db/migrate/20260202171614_create_panda_core_file_categories.rb +30 -0
  208. data/db/migrate/20260203000001_add_user_management_fields_to_panda_core_users.rb +21 -0
  209. data/db/migrate/20260203000002_create_panda_core_user_activities.rb +24 -0
  210. data/db/migrate/20260203000003_create_panda_core_user_sessions.rb +25 -0
  211. data/db/migrate/20260207100000_create_panda_core_presences.rb +21 -0
  212. data/db/migrate/20260213000001_fix_active_storage_attachments_record_id_type.rb +44 -0
  213. data/db/migrate/20260217000001_create_panda_core_feature_flags.rb +14 -0
  214. data/db/migrate/20260303000001_add_metadata_to_panda_core_users.rb +11 -0
  215. data/db/migrate/20260310000001_create_panda_core_tags.rb +27 -0
  216. data/db/migrate/20260310000002_create_panda_core_import_sessions.rb +29 -0
  217. data/lib/generators/panda/core/install_generator.rb +32 -0
  218. data/lib/generators/panda/core/templates/panda.rb +63 -0
  219. data/lib/panda/core/asset_loader.rb +6 -3
  220. data/lib/panda/core/authentication.rb +3 -3
  221. data/lib/panda/core/configuration.rb +195 -1
  222. data/lib/panda/core/engine/admin_controller_config.rb +4 -3
  223. data/lib/panda/core/engine/autoload_config.rb +21 -9
  224. data/lib/panda/core/engine/omniauth_config.rb +114 -32
  225. data/lib/panda/core/engine/pending_migrations_check.rb +46 -0
  226. data/lib/panda/core/engine/route_config.rb +1 -1
  227. data/lib/panda/core/engine/sqlite_schema_compatibility.rb +36 -0
  228. data/lib/panda/core/engine/{phlex_config.rb → view_component_config.rb} +5 -7
  229. data/lib/panda/core/engine.rb +90 -25
  230. data/lib/panda/core/middleware.rb +35 -0
  231. data/lib/panda/core/module_registry.rb +21 -13
  232. data/lib/panda/core/navigation_registry.rb +455 -0
  233. data/lib/panda/core/oauth_providers.rb +27 -0
  234. data/lib/panda/core/permission_registry.rb +89 -0
  235. data/lib/panda/core/search_registry.rb +69 -0
  236. data/lib/panda/core/seeds/file_categories.rb +30 -0
  237. data/lib/panda/core/shared/inflections_config.rb +1 -5
  238. data/lib/panda/core/testing/rails_helper.rb +87 -0
  239. data/lib/panda/core/testing/support/authentication_test_helpers.rb +22 -18
  240. data/lib/panda/core/testing/support/system/cuprite_helpers.rb +4 -4
  241. data/lib/panda/core/testing/view_component_test_controller.rb +19 -0
  242. data/lib/panda/core/version.rb +1 -1
  243. data/lib/panda/core/widget_registry.rb +50 -0
  244. data/lib/panda/core.rb +20 -0
  245. data/lib/tasks/panda/core/file_categories.rake +41 -0
  246. data/public/panda-core-assets/panda-core-0.12.2.css +2 -0
  247. data/public/panda-core-assets/panda-core-0.12.3.css +2 -0
  248. data/public/panda-core-assets/panda-core-0.12.5.css +2 -0
  249. data/public/panda-core-assets/panda-core-0.13.0.css +2 -0
  250. data/public/panda-core-assets/panda-core-0.14.0.css +2 -0
  251. data/public/panda-core-assets/panda-core-0.14.4.css +2 -0
  252. data/public/panda-core-assets/panda-core.css +2 -2
  253. data/public/panda-core-assets/vanilla-calendar.css +47 -0
  254. metadata +205 -26
  255. data/lib/panda/core/engine/inflections_config.rb +0 -23
@@ -5,6 +5,7 @@ require "omniauth"
5
5
 
6
6
  require "panda/core/middleware"
7
7
  require "panda/core/module_registry"
8
+ require "panda/core/search_registry"
8
9
 
9
10
  # Shared engine mixins
10
11
  require_relative "shared/inflections_config"
@@ -14,9 +15,11 @@ require_relative "shared/generator_config"
14
15
  require_relative "engine/autoload_config"
15
16
  require_relative "engine/importmap_config"
16
17
  require_relative "engine/omniauth_config"
17
- require_relative "engine/phlex_config"
18
+ require_relative "engine/view_component_config"
18
19
  require_relative "engine/admin_controller_config"
19
20
  require_relative "engine/route_config"
21
+ require_relative "engine/pending_migrations_check"
22
+ require_relative "engine/sqlite_schema_compatibility"
20
23
 
21
24
  module Panda
22
25
  module Core
@@ -35,9 +38,11 @@ module Panda
35
38
  include AutoloadConfig
36
39
  include ImportmapConfig
37
40
  include OmniauthConfig
38
- include PhlexConfig
41
+ include ViewComponentConfig
39
42
  include AdminControllerConfig
40
43
  include RouteConfig
44
+ include PendingMigrationsCheck
45
+ include SqliteSchemaCompatibility
41
46
 
42
47
  #
43
48
  # Misc configuration point
@@ -46,33 +51,93 @@ module Panda
46
51
  # Intentionally quiet — used as a stable anchor point
47
52
  end
48
53
 
54
+ #
55
+ # Clean up FileCategorization records before blob destruction.
56
+ # Without this, ActiveStorage::Blob#purge silently fails because
57
+ # panda_core_file_categorizations has a FK to active_storage_blobs,
58
+ # and Rails rescues the resulting InvalidForeignKey error.
59
+ #
60
+ initializer "panda_core.active_storage_blob_cleanup" do
61
+ ActiveSupport.on_load(:active_storage_blob) do
62
+ before_destroy :cleanup_file_categorizations
63
+
64
+ private
65
+
66
+ def cleanup_file_categorizations
67
+ Panda::Core::FileCategorization.where(blob_id: id).delete_all
68
+ end
69
+ end
70
+ end
71
+
72
+ #
73
+ # Auto-categorize files when ActiveStorage attachments are created.
74
+ # Maps known attachment types (user avatars, page images, etc.) to
75
+ # their corresponding file categories via FileCategorizer.
76
+ #
77
+ initializer "panda_core.active_storage_categorization" do
78
+ ActiveSupport.on_load(:active_storage_attachment) do
79
+ after_create_commit :auto_categorize_blob
80
+
81
+ private
82
+
83
+ def auto_categorize_blob
84
+ Panda::Core::FileCategorizer.new.categorize_attachment(self)
85
+ rescue => e
86
+ Rails.logger.warn("[Panda::Core] Auto-categorization failed for blob #{blob_id}: #{e.message}")
87
+ end
88
+ end
89
+ end
90
+
91
+ #
92
+ # Ensure system file categories exist on every boot.
93
+ # Skipped in test env to avoid interfering with fixtures.
94
+ # Uses find_or_create_by! so it's idempotent.
95
+ #
96
+ initializer "panda_core.ensure_file_categories", after: "panda_core.active_storage_categorization" do
97
+ config.after_initialize do
98
+ next if Rails.env.test?
99
+ if ActiveRecord::Base.connection.table_exists?("panda_core_file_categories")
100
+ require "panda/core/seeds/file_categories"
101
+ Panda::Core::Seeds::FileCategories.seed!
102
+ end
103
+ rescue ActiveRecord::NoDatabaseError, ActiveRecord::ConnectionNotEstablished
104
+ # Skip when DB isn't available (asset precompilation, etc.)
105
+ end
106
+ end
107
+
49
108
  #
50
109
  # Static asset handling for:
51
110
  # /panda-core-assets
52
111
  #
53
- initializer "panda_core.static_assets" do |app|
54
- Panda::Core::Middleware.use(
55
- app,
56
- Rack::Static,
57
- urls: ["/panda-core-assets"],
58
- root: Panda::Core::Engine.root.join("public"),
59
- header_rules: [
60
- [
61
- :all,
62
- {
63
- "Cache-Control" =>
64
- Rails.env.development? ?
65
- "no-cache, no-store, must-revalidate" :
66
- "public, max-age=31536000"
67
- }
68
- ]
112
+ # Configured during engine load phase to avoid Rails 8.1.2+ frozen middleware stack
113
+ # Use no-cache in development for easier debugging, long cache in production
114
+ config.app_middleware.use(
115
+ Rack::Static,
116
+ urls: ["/panda-core-assets"],
117
+ root: Panda::Core::Engine.root.join("public"),
118
+ header_rules: [
119
+ [
120
+ :all,
121
+ {
122
+ "Cache-Control" => if defined?(::Rails) && ::Rails.env.development?
123
+ "no-cache, no-store, must-revalidate"
124
+ else
125
+ "public, max-age=31536000"
126
+ end
127
+ }
69
128
  ]
70
- )
129
+ ]
130
+ )
131
+
132
+ config.app_middleware.use(Panda::Core::ModuleRegistry::JavaScriptMiddleware)
71
133
 
72
- Panda::Core::Middleware.use(
73
- app,
74
- Panda::Core::ModuleRegistry::JavaScriptMiddleware
75
- )
134
+ #
135
+ # Serve Chartkick vendor assets when the chartkick gem is available
136
+ # Must be inserted BEFORE Rack::Static so it handles chartkick requests
137
+ # before Rack::Static catches all /panda-core-assets/* paths and returns 404
138
+ #
139
+ if Gem.loaded_specs["chartkick"]
140
+ config.app_middleware.insert_before(Rack::Static, Panda::Core::ChartkickAssetMiddleware)
76
141
  end
77
142
  end
78
143
  end
@@ -86,10 +151,10 @@ Panda::Core::ModuleRegistry.register(
86
151
  engine: "Panda::Core::Engine",
87
152
  paths: {
88
153
  builders: "app/builders/panda/core/**/*.rb",
89
- components: "app/components/panda/core/**/*.rb",
154
+ components: "app/components/panda/core/**/*.{rb,erb,js}",
90
155
  helpers: "app/helpers/panda/core/**/*.rb",
91
156
  views: "app/views/panda/core/**/*.erb",
92
157
  layouts: "app/views/layouts/panda/core/**/*.erb",
93
- javascripts: "app/assets/javascript/panda/core/**/*.js"
158
+ javascripts: "app/javascript/panda/core/**/*.js"
94
159
  }
95
160
  )
@@ -142,5 +142,40 @@ module Panda
142
142
  end
143
143
  private_class_method :normalize_stack
144
144
  end
145
+
146
+ # Serves Chartkick vendor JS files (Chart.bundle.js, chartkick.js) from the
147
+ # chartkick gem's vendor directory at /panda-core-assets/chartkick/*.
148
+ class ChartkickAssetMiddleware
149
+ CHARTKICK_PATH_PREFIX = "/panda-core-assets/chartkick/"
150
+ ALLOWED_FILES = %w[Chart.bundle.js chartkick.js].freeze
151
+
152
+ def initialize(app)
153
+ @app = app
154
+ @vendor_root = Pathname.new(Gem.loaded_specs["chartkick"].gem_dir).join("vendor/assets/javascripts")
155
+ end
156
+
157
+ def call(env)
158
+ path = Rack::Utils.unescape_path(env[Rack::PATH_INFO])
159
+
160
+ if path.start_with?(CHARTKICK_PATH_PREFIX)
161
+ filename = path.delete_prefix(CHARTKICK_PATH_PREFIX)
162
+ return @app.call(env) unless ALLOWED_FILES.include?(filename)
163
+
164
+ file_path = @vendor_root.join(filename)
165
+ return @app.call(env) unless file_path.file?
166
+
167
+ content = File.binread(file_path)
168
+ cache = Rails.env.local? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000"
169
+
170
+ [200, {
171
+ "Content-Type" => "application/javascript; charset=utf-8",
172
+ "Content-Length" => content.bytesize.to_s,
173
+ "Cache-Control" => cache
174
+ }, [content]]
175
+ else
176
+ @app.call(env)
177
+ end
178
+ end
179
+ end
145
180
  end
146
181
  end
@@ -343,6 +343,10 @@ module Panda
343
343
  private
344
344
 
345
345
  def find_javascript_file(relative_path)
346
+ # Build list of paths to try - include .js extension fallback
347
+ paths_to_try = [relative_path]
348
+ paths_to_try << "#{relative_path}.js" unless relative_path.end_with?(".js")
349
+
346
350
  # Check each registered module's JavaScript directory
347
351
  ModuleRegistry.modules.each do |gem_name, info|
348
352
  next unless ModuleRegistry.send(:engine_available?, info[:engine])
@@ -350,24 +354,28 @@ module Panda
350
354
  root = ModuleRegistry.send(:engine_root, info[:engine])
351
355
  next unless root
352
356
 
353
- # Check in app/javascript/panda/ (primary location)
354
- candidate = root.join("app/javascript/panda", relative_path)
355
- return candidate.to_s if candidate.exist? && candidate.file?
357
+ paths_to_try.each do |path|
358
+ # Check in app/javascript/panda/ (primary location)
359
+ candidate = root.join("app/javascript/panda", path)
360
+ return candidate.to_s if candidate.exist? && candidate.file?
356
361
 
357
- # Fallback to public/panda/ (for CI environments where assets are copied)
358
- public_candidate = root.join("public/panda", relative_path)
359
- return public_candidate.to_s if public_candidate.exist? && public_candidate.file?
362
+ # Fallback to public/panda/ (for CI environments where assets are copied)
363
+ public_candidate = root.join("public/panda", path)
364
+ return public_candidate.to_s if public_candidate.exist? && public_candidate.file?
365
+ end
360
366
  end
361
367
 
362
368
  # Also check Rails.root if available (for dummy apps in CI)
363
369
  if defined?(Rails.root)
364
- # Check app/javascript/panda/ in Rails.root
365
- rails_candidate = Rails.root.join("app/javascript/panda", relative_path)
366
- return rails_candidate.to_s if rails_candidate.exist? && rails_candidate.file?
367
-
368
- # Fallback to public/panda/ in Rails.root
369
- rails_public_candidate = Rails.root.join("public/panda", relative_path)
370
- return rails_public_candidate.to_s if rails_public_candidate.exist? && rails_public_candidate.file?
370
+ paths_to_try.each do |path|
371
+ # Check app/javascript/panda/ in Rails.root
372
+ rails_candidate = Rails.root.join("app/javascript/panda", path)
373
+ return rails_candidate.to_s if rails_candidate.exist? && rails_candidate.file?
374
+
375
+ # Fallback to public/panda/ in Rails.root
376
+ rails_public_candidate = Rails.root.join("public/panda", path)
377
+ return rails_public_candidate.to_s if rails_public_candidate.exist? && rails_public_candidate.file?
378
+ end
371
379
  end
372
380
 
373
381
  nil