rails_pulse 0.1.4 → 0.2.3

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +78 -0
  3. data/Rakefile +152 -3
  4. data/app/assets/images/rails_pulse/rails-pulse-logo.png +0 -0
  5. data/app/assets/stylesheets/rails_pulse/components/datepicker.css +191 -0
  6. data/app/assets/stylesheets/rails_pulse/components/switch.css +36 -0
  7. data/app/assets/stylesheets/rails_pulse/components/tags.css +98 -0
  8. data/app/assets/stylesheets/rails_pulse/components/utilities.css +26 -0
  9. data/app/controllers/concerns/chart_table_concern.rb +2 -0
  10. data/app/controllers/concerns/response_range_concern.rb +15 -2
  11. data/app/controllers/concerns/tag_filter_concern.rb +26 -0
  12. data/app/controllers/concerns/time_range_concern.rb +27 -8
  13. data/app/controllers/rails_pulse/application_controller.rb +74 -0
  14. data/app/controllers/rails_pulse/dashboard_controller.rb +12 -8
  15. data/app/controllers/rails_pulse/queries_controller.rb +15 -7
  16. data/app/controllers/rails_pulse/requests_controller.rb +48 -12
  17. data/app/controllers/rails_pulse/routes_controller.rb +14 -5
  18. data/app/controllers/rails_pulse/tags_controller.rb +51 -0
  19. data/app/helpers/rails_pulse/application_helper.rb +2 -0
  20. data/app/helpers/rails_pulse/form_helper.rb +75 -0
  21. data/app/helpers/rails_pulse/tags_helper.rb +29 -0
  22. data/app/javascript/rails_pulse/application.js +6 -0
  23. data/app/javascript/rails_pulse/controllers/custom_range_controller.js +115 -0
  24. data/app/javascript/rails_pulse/controllers/datepicker_controller.js +48 -0
  25. data/app/javascript/rails_pulse/controllers/global_filters_controller.js +110 -0
  26. data/app/models/concerns/rails_pulse/taggable.rb +63 -0
  27. data/app/models/rails_pulse/dashboard/charts/average_response_time.rb +12 -5
  28. data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +12 -5
  29. data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +7 -0
  30. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +6 -0
  31. data/app/models/rails_pulse/queries/cards/average_query_times.rb +10 -6
  32. data/app/models/rails_pulse/queries/cards/execution_rate.rb +16 -10
  33. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +10 -6
  34. data/app/models/rails_pulse/queries/charts/average_query_times.rb +5 -2
  35. data/app/models/rails_pulse/queries/tables/index.rb +20 -2
  36. data/app/models/rails_pulse/query.rb +2 -0
  37. data/app/models/rails_pulse/request.rb +9 -1
  38. data/app/models/rails_pulse/requests/charts/average_response_times.rb +12 -6
  39. data/app/models/rails_pulse/route.rb +2 -0
  40. data/app/models/rails_pulse/routes/cards/average_response_times.rb +10 -6
  41. data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +10 -6
  42. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +10 -6
  43. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +10 -6
  44. data/app/models/rails_pulse/routes/charts/average_response_times.rb +9 -5
  45. data/app/models/rails_pulse/routes/tables/index.rb +20 -2
  46. data/app/models/rails_pulse/summary.rb +55 -0
  47. data/app/services/rails_pulse/summary_service.rb +2 -0
  48. data/app/views/layouts/rails_pulse/_global_filters.html.erb +91 -0
  49. data/app/views/layouts/rails_pulse/_menu_items.html.erb +5 -5
  50. data/app/views/layouts/rails_pulse/application.html.erb +8 -5
  51. data/app/views/rails_pulse/components/_active_filters.html.erb +36 -0
  52. data/app/views/rails_pulse/components/_page_header.html.erb +24 -0
  53. data/app/views/rails_pulse/dashboard/index.html.erb +4 -0
  54. data/app/views/rails_pulse/operations/show.html.erb +1 -1
  55. data/app/views/rails_pulse/queries/_table.html.erb +3 -1
  56. data/app/views/rails_pulse/queries/index.html.erb +3 -7
  57. data/app/views/rails_pulse/queries/show.html.erb +3 -7
  58. data/app/views/rails_pulse/requests/_table.html.erb +3 -1
  59. data/app/views/rails_pulse/requests/index.html.erb +44 -62
  60. data/app/views/rails_pulse/requests/show.html.erb +1 -1
  61. data/app/views/rails_pulse/routes/_requests_table.html.erb +3 -1
  62. data/app/views/rails_pulse/routes/_table.html.erb +3 -1
  63. data/app/views/rails_pulse/routes/index.html.erb +4 -8
  64. data/app/views/rails_pulse/routes/show.html.erb +3 -7
  65. data/app/views/rails_pulse/tags/_tag_manager.html.erb +73 -0
  66. data/config/initializers/rails_charts_csp_patch.rb +9 -9
  67. data/config/routes.rb +5 -0
  68. data/db/rails_pulse_schema.rb +3 -0
  69. data/lib/generators/rails_pulse/install_generator.rb +21 -2
  70. data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +3 -0
  71. data/lib/generators/rails_pulse/templates/rails_pulse.rb +21 -0
  72. data/lib/generators/rails_pulse/upgrade_generator.rb +145 -29
  73. data/lib/rails_pulse/cleanup_service.rb +8 -0
  74. data/lib/rails_pulse/configuration.rb +16 -1
  75. data/lib/rails_pulse/engine.rb +25 -0
  76. data/lib/rails_pulse/version.rb +1 -1
  77. data/public/rails-pulse-assets/rails-pulse-icons.js +16 -15
  78. data/public/rails-pulse-assets/rails-pulse-icons.js.map +1 -1
  79. data/public/rails-pulse-assets/rails-pulse.css +1 -1
  80. data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
  81. data/public/rails-pulse-assets/rails-pulse.js +73 -69
  82. data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
  83. metadata +18 -3
  84. data/app/views/rails_pulse/components/_breadcrumbs.html.erb +0 -12
@@ -41,17 +41,36 @@ module RailsPulse
41
41
  # Check for existing Rails Pulse tables
42
42
  tables_exist = rails_pulse_tables_exist?
43
43
 
44
- if !tables_exist && File.exist?("db/rails_pulse_schema.rb")
44
+ root_path = respond_to?(:destination_root) ? destination_root : Rails.root
45
+ schema_path = File.join(root_path, "db/rails_pulse_schema.rb")
46
+
47
+ if !tables_exist && File.exist?(schema_path)
45
48
  :schema_only
46
49
  elsif !tables_exist
47
50
  :not_installed
48
- elsif File.exist?("db/rails_pulse_migrate")
51
+ elsif has_separate_database_config?
49
52
  :separate
50
53
  else
51
54
  :single
52
55
  end
53
56
  end
54
57
 
58
+ def has_separate_database_config?
59
+ root_path = respond_to?(:destination_root) ? destination_root : Rails.root
60
+ config_path = File.join(root_path, "config/database.yml")
61
+
62
+ return false unless File.exist?(config_path)
63
+
64
+ require "yaml"
65
+ db_config = YAML.load_file(config_path)
66
+
67
+ # Check if any environment has a rails_pulse database configuration
68
+ db_config.values.any? { |env| env.is_a?(Hash) && env.key?("rails_pulse") }
69
+ rescue => e
70
+ # If we can't read the file, assume single database
71
+ false
72
+ end
73
+
55
74
  def rails_pulse_tables_exist?
56
75
  return false unless defined?(ActiveRecord::Base)
57
76
 
@@ -65,7 +84,8 @@ module RailsPulse
65
84
 
66
85
  def get_rails_pulse_table_names
67
86
  # Load the schema file to get the table names dynamically
68
- schema_file = File.join(Rails.root, "db/rails_pulse_schema.rb")
87
+ root_path = respond_to?(:destination_root) ? destination_root : Rails.root
88
+ schema_file = File.join(root_path, "db/rails_pulse_schema.rb")
69
89
 
70
90
  if File.exist?(schema_file)
71
91
  # Read the schema file and extract the required_tables array
@@ -84,44 +104,114 @@ module RailsPulse
84
104
  end
85
105
 
86
106
  def upgrade_single_database
87
- missing_columns = detect_missing_columns
107
+ # Check for new migrations in gem
108
+ gem_migrations = get_gem_migrations
109
+ existing_migrations = get_user_migrations("db/migrate")
110
+ new_migrations = gem_migrations - existing_migrations
111
+
112
+ if new_migrations.any?
113
+ say "Found #{new_migrations.size} new migration(s) to copy:", :blue
114
+ new_migrations.each do |migration|
115
+ say " - #{migration}", :blue
116
+ copy_gem_migration_to(migration, "db/migrate")
117
+ end
88
118
 
89
- if missing_columns.empty?
90
- say "Rails Pulse is up to date! No migration needed.", :green
91
- return
92
- end
119
+ say "\nMigrations copied successfully!", :green
120
+ say "\nNext steps:", :green
121
+ say "1. Run: rails db:migrate"
122
+ say "2. Restart your Rails server"
123
+ else
124
+ # Fall back to detecting missing columns
125
+ missing_columns = detect_missing_columns
93
126
 
94
- # Format missing columns by table for the template
95
- missing_by_table = format_missing_columns_by_table(missing_columns)
127
+ if missing_columns.empty?
128
+ say "Rails Pulse is up to date! No migration needed.", :green
129
+ return
130
+ end
96
131
 
97
- say "Creating upgrade migration for missing columns: #{missing_columns.keys.join(', ')}", :blue
132
+ # Format missing columns by table for the template
133
+ missing_by_table = format_missing_columns_by_table(missing_columns)
98
134
 
99
- # Set instance variables for template
100
- @migration_version = ActiveRecord::Migration.current_version
101
- @missing_columns = missing_by_table
135
+ say "Creating upgrade migration for missing columns: #{missing_columns.keys.join(', ')}", :blue
102
136
 
103
- migration_template(
104
- "migrations/upgrade_rails_pulse_tables.rb",
105
- "db/migrate/upgrade_rails_pulse_tables.rb"
106
- )
137
+ # Set instance variables for template
138
+ @migration_version = ActiveRecord::Migration.current_version
139
+ @missing_columns = missing_by_table
107
140
 
108
- say <<~MESSAGE
141
+ migration_template(
142
+ "migrations/upgrade_rails_pulse_tables.rb",
143
+ "db/migrate/upgrade_rails_pulse_tables.rb"
144
+ )
109
145
 
110
- Upgrade migration created successfully!
146
+ say <<~MESSAGE
111
147
 
112
- Next steps:
113
- 1. Run: rails db:migrate
114
- 2. Restart your Rails server
148
+ Upgrade migration created successfully!
115
149
 
116
- This migration will add: #{missing_columns.keys.join(', ')}
150
+ Next steps:
151
+ 1. Run: rails db:migrate
152
+ 2. Restart your Rails server
117
153
 
118
- MESSAGE
154
+ This migration will add: #{missing_columns.keys.join(', ')}
155
+
156
+ MESSAGE
157
+ end
119
158
  end
120
159
 
121
160
  def upgrade_separate_database
122
- # For separate database, we'd need to check the schema file and generate migrations
123
- # in db/rails_pulse_migrate/ directory
124
- say "Separate database upgrade not implemented yet. Please check db/rails_pulse_schema.rb for updates.", :yellow
161
+ # Check for new migrations in gem
162
+ gem_migrations = get_gem_migrations
163
+ existing_migrations = get_user_migrations("db/rails_pulse_migrate")
164
+ new_migrations = gem_migrations - existing_migrations
165
+
166
+ if new_migrations.any?
167
+ say "Found #{new_migrations.size} new migration(s) to copy:", :blue
168
+ new_migrations.each do |migration|
169
+ say " - #{migration}", :blue
170
+ copy_gem_migration_to(migration, "db/rails_pulse_migrate")
171
+ end
172
+
173
+ say "\nMigrations copied successfully!", :green
174
+ say "\nNext steps:", :green
175
+ say "1. Run migrations for the rails_pulse database:"
176
+ say " rails db:migrate (will run migrations for all databases)"
177
+ say " OR manually run the migration files in db/rails_pulse_migrate/"
178
+ say "2. Restart your Rails server"
179
+ else
180
+ # Fall back to detecting missing columns
181
+ missing_columns = detect_missing_columns
182
+
183
+ if missing_columns.empty?
184
+ say "Rails Pulse is up to date! No migrations needed.", :green
185
+ else
186
+ # Format missing columns by table for the template
187
+ missing_by_table = format_missing_columns_by_table(missing_columns)
188
+
189
+ say "Creating upgrade migration for missing columns: #{missing_columns.keys.join(', ')}", :blue
190
+
191
+ # Set instance variables for template
192
+ @migration_version = ActiveRecord::Migration.current_version
193
+ @missing_columns = missing_by_table
194
+
195
+ migration_template(
196
+ "migrations/upgrade_rails_pulse_tables.rb",
197
+ "db/rails_pulse_migrate/upgrade_rails_pulse_tables.rb"
198
+ )
199
+
200
+ say <<~MESSAGE
201
+
202
+ Upgrade migration created successfully!
203
+
204
+ Next steps:
205
+ 1. Run migrations for the rails_pulse database:
206
+ rails db:migrate (will run migrations for all databases)
207
+ OR manually run the migration files in db/rails_pulse_migrate/
208
+ 2. Restart your Rails server
209
+
210
+ This migration will add: #{missing_columns.keys.join(', ')}
211
+
212
+ MESSAGE
213
+ end
214
+ end
125
215
  end
126
216
 
127
217
  def offer_conversion_to_migrations
@@ -165,7 +255,8 @@ module RailsPulse
165
255
  end
166
256
 
167
257
  def get_expected_schema_from_file
168
- schema_file = File.join(Rails.root, "db/rails_pulse_schema.rb")
258
+ root_path = respond_to?(:destination_root) ? destination_root : Rails.root
259
+ schema_file = File.join(root_path, "db/rails_pulse_schema.rb")
169
260
  return {} unless File.exist?(schema_file)
170
261
 
171
262
  schema_content = File.read(schema_file)
@@ -221,6 +312,31 @@ module RailsPulse
221
312
 
222
313
  missing_by_table
223
314
  end
315
+
316
+ def get_gem_migrations
317
+ gem_migrations_path = File.expand_path("../../../db/rails_pulse_migrate", __dir__)
318
+ return [] unless File.directory?(gem_migrations_path)
319
+
320
+ Dir.glob("#{gem_migrations_path}/*.rb").map { |f| File.basename(f) }
321
+ end
322
+
323
+ def get_user_migrations(directory)
324
+ # Use destination_root in tests, Rails.root in production
325
+ root_path = respond_to?(:destination_root) ? destination_root : Rails.root
326
+ full_directory = File.join(root_path, directory)
327
+
328
+ return [] unless File.directory?(full_directory)
329
+
330
+ Dir.glob("#{full_directory}/*.rb").map { |f| File.basename(f) }
331
+ end
332
+
333
+ def copy_gem_migration_to(migration_name, destination)
334
+ gem_migrations_path = File.expand_path("../../../db/rails_pulse_migrate", __dir__)
335
+ source_file = File.join(gem_migrations_path, migration_name)
336
+ destination_file = File.join(destination, migration_name)
337
+
338
+ copy_file source_file, destination_file
339
+ end
224
340
  end
225
341
  end
226
342
  end
@@ -69,6 +69,11 @@ module RailsPulse
69
69
  return 0 unless defined?(RailsPulse::Request)
70
70
 
71
71
  count = RailsPulse::Request.where("occurred_at < ?", cutoff_time).count
72
+ request_ids = RailsPulse::Request.where("occurred_at < ?", cutoff_time).pluck(:id)
73
+
74
+ # Delete associated operations first to respect foreign key constraints
75
+ RailsPulse::Operation.where(request_id: request_ids).delete_all
76
+
72
77
  RailsPulse::Request.where("occurred_at < ?", cutoff_time).delete_all
73
78
  count
74
79
  end
@@ -140,6 +145,9 @@ module RailsPulse
140
145
  .limit(records_to_delete)
141
146
  .pluck(:id)
142
147
 
148
+ # Delete associated operations first to respect foreign key constraints
149
+ RailsPulse::Operation.where(request_id: ids_to_delete).delete_all
150
+
143
151
  RailsPulse::Request.where(id: ids_to_delete).delete_all
144
152
  records_to_delete
145
153
  end
@@ -16,7 +16,8 @@ module RailsPulse
16
16
  :connects_to,
17
17
  :authentication_enabled,
18
18
  :authentication_method,
19
- :authentication_redirect_path
19
+ :authentication_redirect_path,
20
+ :tags
20
21
 
21
22
  def initialize
22
23
  @enabled = true
@@ -41,6 +42,7 @@ module RailsPulse
41
42
  @authentication_enabled = Rails.env.production?
42
43
  @authentication_method = nil
43
44
  @authentication_redirect_path = "/"
45
+ @tags = [ "ignored", "critical", "experimental" ]
44
46
 
45
47
  validate_configuration!
46
48
  end
@@ -64,6 +66,7 @@ module RailsPulse
64
66
  validate_patterns!
65
67
  validate_database_settings!
66
68
  validate_authentication_settings!
69
+ validate_tags!
67
70
  end
68
71
 
69
72
  # Revalidate configuration after changes
@@ -135,6 +138,18 @@ module RailsPulse
135
138
  end
136
139
  end
137
140
 
141
+ def validate_tags!
142
+ unless @tags.is_a?(Array)
143
+ raise ArgumentError, "tags must be an array, got #{@tags.class}"
144
+ end
145
+
146
+ @tags.each do |tag|
147
+ unless tag.is_a?(String)
148
+ raise ArgumentError, "tags must be strings, got #{tag.class}"
149
+ end
150
+ end
151
+ end
152
+
138
153
  # Default patterns for common asset types and paths
139
154
  def default_asset_patterns
140
155
  [
@@ -14,6 +14,17 @@ module RailsPulse
14
14
  class Engine < ::Rails::Engine
15
15
  isolate_namespace RailsPulse
16
16
 
17
+ # Prevent rails_charts from polluting the global ActionView namespace
18
+ # This MUST happen before any initializers run to avoid conflicts with host apps
19
+ # that use Chartkick or other chart libraries
20
+ if defined?(RailsCharts::Engine)
21
+ # Find and remove the rails_charts.helpers initializer
22
+ RailsCharts::Engine.initializers.delete_if do |init|
23
+ init.name == "rails_charts.helpers"
24
+ end
25
+ end
26
+
27
+
17
28
  # Load Rake tasks
18
29
  rake_tasks do
19
30
  Dir.glob(File.expand_path("../tasks/**/*.rake", __FILE__)).each { |file| load file }
@@ -50,6 +61,20 @@ module RailsPulse
50
61
  RailsCharts.options[:theme] = "railspulse"
51
62
  end
52
63
 
64
+ # Manually include RailsCharts helpers only in RailsPulse views
65
+ # This ensures rails_charts methods are only available in RailsPulse namespace,
66
+ # not in the host application
67
+ initializer "rails_pulse.include_rails_charts_helpers" do
68
+ ActiveSupport.on_load :action_view do
69
+ if defined?(RailsCharts::Helpers) && defined?(RailsPulse::ChartHelper)
70
+ unless RailsPulse::ChartHelper.include?(RailsCharts::Helpers)
71
+ RailsPulse::ChartHelper.include(RailsCharts::Helpers)
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+
53
78
  initializer "rails_pulse.ransack", after: "ransack.initialize" do
54
79
  # Ensure Ransack is loaded before our models
55
80
  end
@@ -1,3 +1,3 @@
1
1
  module RailsPulse
2
- VERSION = "0.1.4"
2
+ VERSION = "0.2.3"
3
3
  end
@@ -1,44 +1,45 @@
1
1
  // Rails Pulse Icons Bundle - Auto-generated
2
- // Contains 33 SVG icons for Rails Pulse
2
+ // Contains 34 SVG icons for Rails Pulse
3
3
 
4
4
  (function() {
5
5
  'use strict';
6
6
 
7
7
  // Icon data
8
8
  const icons = {
9
- "menu": "<line x1=\"4\" x2=\"20\" y1=\"12\" y2=\"12\" /><line x1=\"4\" x2=\"20\" y1=\"6\" y2=\"6\" /><line x1=\"4\" x2=\"20\" y1=\"18\" y2=\"18\" />",
9
+ "menu": "<path d=\"M4 5h16\" /><path d=\"M4 12h16\" /><path d=\"M4 19h16\" />",
10
10
  "sun": "<circle cx=\"12\" cy=\"12\" r=\"4\" /><path d=\"M12 2v2\" /><path d=\"M12 20v2\" /><path d=\"m4.93 4.93 1.41 1.41\" /><path d=\"m17.66 17.66 1.41 1.41\" /><path d=\"M2 12h2\" /><path d=\"M20 12h2\" /><path d=\"m6.34 17.66-1.41 1.41\" /><path d=\"m19.07 4.93-1.41 1.41\" />",
11
- "moon": "<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\" />",
11
+ "moon": "<path d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\" />",
12
12
  "chevron-right": "<path d=\"m9 18 6-6-6-6\" />",
13
13
  "chevron-left": "<path d=\"m15 18-6-6 6-6\" />",
14
14
  "chevron-down": "<path d=\"m6 9 6 6 6-6\" />",
15
15
  "chevron-up": "<path d=\"m18 15-6-6-6 6\" />",
16
16
  "chevrons-left": "<path d=\"m11 17-5-5 5-5\" /><path d=\"m18 17-5-5 5-5\" />",
17
17
  "chevrons-right": "<path d=\"m6 17 5-5-5-5\" /><path d=\"m13 17 5-5-5-5\" />",
18
- "loader-circle": "<line x1=\"12\" x2=\"12\" y1=\"2\" y2=\"6\" /><line x1=\"12\" x2=\"12\" y1=\"18\" y2=\"22\" /><line x1=\"4.93\" x2=\"7.76\" y1=\"4.93\" y2=\"7.76\" /><line x1=\"16.24\" x2=\"19.07\" y1=\"16.24\" y2=\"19.07\" /><line x1=\"2\" x2=\"6\" y1=\"12\" y2=\"12\" /><line x1=\"18\" x2=\"22\" y1=\"12\" y2=\"12\" /><line x1=\"4.93\" x2=\"7.76\" y1=\"19.07\" y2=\"16.24\" /><line x1=\"16.24\" x2=\"19.07\" y1=\"7.76\" y2=\"4.93\" />",
19
- "search": "<circle cx=\"11\" cy=\"11\" r=\"8\" /><path d=\"m21 21-4.3-4.3\" />",
20
- "filter": "<polygon points=\"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3\" />",
18
+ "loader-circle": "<path d=\"M12 2v4\" /><path d=\"m16.2 7.8 2.9-2.9\" /><path d=\"M18 12h4\" /><path d=\"m16.2 16.2 2.9 2.9\" /><path d=\"M12 18v4\" /><path d=\"m4.9 19.1 2.9-2.9\" /><path d=\"M2 12h4\" /><path d=\"m4.9 4.9 2.9 2.9\" />",
19
+ "search": "<path d=\"m21 21-4.34-4.34\" /><circle cx=\"11\" cy=\"11\" r=\"8\" />",
20
+ "list-filter": "<path d=\"M2 5h20\" /><path d=\"M6 12h12\" /><path d=\"M9 19h6\" />",
21
+ "list-filter-plus": "<path d=\"M12 5H2\" /><path d=\"M6 12h12\" /><path d=\"M9 19h6\" /><path d=\"M16 5h6\" /><path d=\"M19 8V2\" />",
21
22
  "x": "<path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />",
22
23
  "x-circle": "<circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"m15 9-6 6\" /><path d=\"m9 9 6 6\" />",
23
24
  "check": "<path d=\"M20 6 9 17l-5-5\" />",
24
25
  "alert-circle": "<circle cx=\"12\" cy=\"12\" r=\"10\" /><line x1=\"12\" x2=\"12\" y1=\"8\" y2=\"12\" /><line x1=\"12\" x2=\"12.01\" y1=\"16\" y2=\"16\" />",
25
- "alert-triangle": "<path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z\" /><path d=\"M12 9v4\" /><path d=\"M12 17h.01\" />",
26
+ "alert-triangle": "<path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" /><path d=\"M12 9v4\" /><path d=\"M12 17h.01\" />",
26
27
  "info": "<circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"M12 16v-4\" /><path d=\"M12 8h.01\" />",
27
- "external-link": "<path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" /><polyline points=\"15 3 21 3 21 9\" /><line x1=\"10\" x2=\"21\" y1=\"14\" y2=\"3\" />",
28
- "download": "<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" /><polyline points=\"7 10 12 15 17 10\" /><line x1=\"12\" x2=\"12\" y1=\"15\" y2=\"3\" />",
28
+ "external-link": "<path d=\"M15 3h6v6\" /><path d=\"M10 14 21 3\" /><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" />",
29
+ "download": "<path d=\"M12 15V3\" /><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" /><path d=\"m7 10 5 5 5-5\" />",
29
30
  "refresh-cw": "<path d=\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\" /><path d=\"M21 3v5h-5\" /><path d=\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\" /><path d=\"M8 16H3v5\" />",
30
- "clock": "<circle cx=\"12\" cy=\"12\" r=\"10\" /><polyline points=\"12 6 12 12 16 14\" />",
31
+ "clock": "<path d=\"M12 6v6l4 2\" /><circle cx=\"12\" cy=\"12\" r=\"10\" />",
31
32
  "database": "<ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\" /><path d=\"M3 5V19A9 3 0 0 0 21 19V5\" /><path d=\"M3 12A9 3 0 0 0 21 12\" />",
32
33
  "server": "<rect width=\"20\" height=\"8\" x=\"2\" y=\"2\" rx=\"2\" ry=\"2\" /><rect width=\"20\" height=\"8\" x=\"2\" y=\"14\" rx=\"2\" ry=\"2\" /><line x1=\"6\" x2=\"6.01\" y1=\"6\" y2=\"6\" /><line x1=\"6\" x2=\"6.01\" y1=\"18\" y2=\"18\" />",
33
- "activity": "<path d=\"M22 12h-4l-3 9L9 3l-3 9H2\" />",
34
+ "activity": "<path d=\"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2\" />",
34
35
  "layout-dashboard": "<rect width=\"7\" height=\"9\" x=\"3\" y=\"3\" rx=\"1\" /><rect width=\"7\" height=\"5\" x=\"14\" y=\"3\" rx=\"1\" /><rect width=\"7\" height=\"9\" x=\"14\" y=\"12\" rx=\"1\" /><rect width=\"7\" height=\"5\" x=\"3\" y=\"16\" rx=\"1\" />",
35
36
  "audio-lines": "<path d=\"M2 10v3\" /><path d=\"M6 6v11\" /><path d=\"M10 3v18\" /><path d=\"M14 8v7\" /><path d=\"M18 5v13\" /><path d=\"M22 10v3\" />",
36
- "message-circle-question": "<path d=\"M7.9 20A9 9 0 1 0 4 16.1L2 22Z\" /><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\" /><path d=\"M12 17h.01\" />",
37
+ "message-circle-question": "<path d=\"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719\" /><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\" /><path d=\"M12 17h.01\" />",
37
38
  "route": "<circle cx=\"6\" cy=\"19\" r=\"3\" /><path d=\"M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15\" /><circle cx=\"18\" cy=\"5\" r=\"3\" />",
38
- "trending-up": "<polyline points=\"22 7 13.5 15.5 8.5 10.5 2 17\" /><polyline points=\"16 7 22 7 22 13\" />",
39
- "trending-down": "<polyline points=\"22 17 13.5 8.5 8.5 13.5 2 7\" /><polyline points=\"16 17 22 17 22 11\" />",
39
+ "trending-up": "<path d=\"M16 7h6v6\" /><path d=\"m22 7-8.5 8.5-5-5L2 17\" />",
40
+ "trending-down": "<path d=\"M16 17h6v-6\" /><path d=\"m22 17-8.5-8.5-5 5L2 7\" />",
40
41
  "move-right": "<path d=\"M18 8L22 12L18 16\" /><path d=\"M2 12H22\" />",
41
- "eye": "<path d=\"M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z\" /><circle cx=\"12\" cy=\"12\" r=\"3\" />"
42
+ "eye": "<path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\" /><circle cx=\"12\" cy=\"12\" r=\"3\" />"
42
43
  };
43
44
 
44
45
  // Global icon registry
@@ -8,6 +8,6 @@
8
8
  "names": [],
9
9
  "mappings": "",
10
10
  "sourcesContent": [
11
- "// Rails Pulse Icons Bundle - Auto-generated\n// Contains 33 SVG icons for Rails Pulse\n\n(function() {\n 'use strict';\n\n // Icon data\n const icons = {\n \"menu\": \"<line x1=\\\"4\\\" x2=\\\"20\\\" y1=\\\"12\\\" y2=\\\"12\\\" /><line x1=\\\"4\\\" x2=\\\"20\\\" y1=\\\"6\\\" y2=\\\"6\\\" /><line x1=\\\"4\\\" x2=\\\"20\\\" y1=\\\"18\\\" y2=\\\"18\\\" />\",\n \"sun\": \"<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"4\\\" /><path d=\\\"M12 2v2\\\" /><path d=\\\"M12 20v2\\\" /><path d=\\\"m4.93 4.93 1.41 1.41\\\" /><path d=\\\"m17.66 17.66 1.41 1.41\\\" /><path d=\\\"M2 12h2\\\" /><path d=\\\"M20 12h2\\\" /><path d=\\\"m6.34 17.66-1.41 1.41\\\" /><path d=\\\"m19.07 4.93-1.41 1.41\\\" />\",\n \"moon\": \"<path d=\\\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\\\" />\",\n \"chevron-right\": \"<path d=\\\"m9 18 6-6-6-6\\\" />\",\n \"chevron-left\": \"<path d=\\\"m15 18-6-6 6-6\\\" />\",\n \"chevron-down\": \"<path d=\\\"m6 9 6 6 6-6\\\" />\",\n \"chevron-up\": \"<path d=\\\"m18 15-6-6-6 6\\\" />\",\n \"chevrons-left\": \"<path d=\\\"m11 17-5-5 5-5\\\" /><path d=\\\"m18 17-5-5 5-5\\\" />\",\n \"chevrons-right\": \"<path d=\\\"m6 17 5-5-5-5\\\" /><path d=\\\"m13 17 5-5-5-5\\\" />\",\n \"loader-circle\": \"<line x1=\\\"12\\\" x2=\\\"12\\\" y1=\\\"2\\\" y2=\\\"6\\\" /><line x1=\\\"12\\\" x2=\\\"12\\\" y1=\\\"18\\\" y2=\\\"22\\\" /><line x1=\\\"4.93\\\" x2=\\\"7.76\\\" y1=\\\"4.93\\\" y2=\\\"7.76\\\" /><line x1=\\\"16.24\\\" x2=\\\"19.07\\\" y1=\\\"16.24\\\" y2=\\\"19.07\\\" /><line x1=\\\"2\\\" x2=\\\"6\\\" y1=\\\"12\\\" y2=\\\"12\\\" /><line x1=\\\"18\\\" x2=\\\"22\\\" y1=\\\"12\\\" y2=\\\"12\\\" /><line x1=\\\"4.93\\\" x2=\\\"7.76\\\" y1=\\\"19.07\\\" y2=\\\"16.24\\\" /><line x1=\\\"16.24\\\" x2=\\\"19.07\\\" y1=\\\"7.76\\\" y2=\\\"4.93\\\" />\",\n \"search\": \"<circle cx=\\\"11\\\" cy=\\\"11\\\" r=\\\"8\\\" /><path d=\\\"m21 21-4.3-4.3\\\" />\",\n \"filter\": \"<polygon points=\\\"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3\\\" />\",\n \"x\": \"<path d=\\\"M18 6 6 18\\\" /><path d=\\\"m6 6 12 12\\\" />\",\n \"x-circle\": \"<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\" /><path d=\\\"m15 9-6 6\\\" /><path d=\\\"m9 9 6 6\\\" />\",\n \"check\": \"<path d=\\\"M20 6 9 17l-5-5\\\" />\",\n \"alert-circle\": \"<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\" /><line x1=\\\"12\\\" x2=\\\"12\\\" y1=\\\"8\\\" y2=\\\"12\\\" /><line x1=\\\"12\\\" x2=\\\"12.01\\\" y1=\\\"16\\\" y2=\\\"16\\\" />\",\n \"alert-triangle\": \"<path d=\\\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z\\\" /><path d=\\\"M12 9v4\\\" /><path d=\\\"M12 17h.01\\\" />\",\n \"info\": \"<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\" /><path d=\\\"M12 16v-4\\\" /><path d=\\\"M12 8h.01\\\" />\",\n \"external-link\": \"<path d=\\\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\\\" /><polyline points=\\\"15 3 21 3 21 9\\\" /><line x1=\\\"10\\\" x2=\\\"21\\\" y1=\\\"14\\\" y2=\\\"3\\\" />\",\n \"download\": \"<path d=\\\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\\\" /><polyline points=\\\"7 10 12 15 17 10\\\" /><line x1=\\\"12\\\" x2=\\\"12\\\" y1=\\\"15\\\" y2=\\\"3\\\" />\",\n \"refresh-cw\": \"<path d=\\\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\\\" /><path d=\\\"M21 3v5h-5\\\" /><path d=\\\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\\\" /><path d=\\\"M8 16H3v5\\\" />\",\n \"clock\": \"<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\" /><polyline points=\\\"12 6 12 12 16 14\\\" />\",\n \"database\": \"<ellipse cx=\\\"12\\\" cy=\\\"5\\\" rx=\\\"9\\\" ry=\\\"3\\\" /><path d=\\\"M3 5V19A9 3 0 0 0 21 19V5\\\" /><path d=\\\"M3 12A9 3 0 0 0 21 12\\\" />\",\n \"server\": \"<rect width=\\\"20\\\" height=\\\"8\\\" x=\\\"2\\\" y=\\\"2\\\" rx=\\\"2\\\" ry=\\\"2\\\" /><rect width=\\\"20\\\" height=\\\"8\\\" x=\\\"2\\\" y=\\\"14\\\" rx=\\\"2\\\" ry=\\\"2\\\" /><line x1=\\\"6\\\" x2=\\\"6.01\\\" y1=\\\"6\\\" y2=\\\"6\\\" /><line x1=\\\"6\\\" x2=\\\"6.01\\\" y1=\\\"18\\\" y2=\\\"18\\\" />\",\n \"activity\": \"<path d=\\\"M22 12h-4l-3 9L9 3l-3 9H2\\\" />\",\n \"layout-dashboard\": \"<rect width=\\\"7\\\" height=\\\"9\\\" x=\\\"3\\\" y=\\\"3\\\" rx=\\\"1\\\" /><rect width=\\\"7\\\" height=\\\"5\\\" x=\\\"14\\\" y=\\\"3\\\" rx=\\\"1\\\" /><rect width=\\\"7\\\" height=\\\"9\\\" x=\\\"14\\\" y=\\\"12\\\" rx=\\\"1\\\" /><rect width=\\\"7\\\" height=\\\"5\\\" x=\\\"3\\\" y=\\\"16\\\" rx=\\\"1\\\" />\",\n \"audio-lines\": \"<path d=\\\"M2 10v3\\\" /><path d=\\\"M6 6v11\\\" /><path d=\\\"M10 3v18\\\" /><path d=\\\"M14 8v7\\\" /><path d=\\\"M18 5v13\\\" /><path d=\\\"M22 10v3\\\" />\",\n \"message-circle-question\": \"<path d=\\\"M7.9 20A9 9 0 1 0 4 16.1L2 22Z\\\" /><path d=\\\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\\\" /><path d=\\\"M12 17h.01\\\" />\",\n \"route\": \"<circle cx=\\\"6\\\" cy=\\\"19\\\" r=\\\"3\\\" /><path d=\\\"M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15\\\" /><circle cx=\\\"18\\\" cy=\\\"5\\\" r=\\\"3\\\" />\",\n \"trending-up\": \"<polyline points=\\\"22 7 13.5 15.5 8.5 10.5 2 17\\\" /><polyline points=\\\"16 7 22 7 22 13\\\" />\",\n \"trending-down\": \"<polyline points=\\\"22 17 13.5 8.5 8.5 13.5 2 7\\\" /><polyline points=\\\"16 17 22 17 22 11\\\" />\",\n \"move-right\": \"<path d=\\\"M18 8L22 12L18 16\\\" /><path d=\\\"M2 12H22\\\" />\",\n \"eye\": \"<path d=\\\"M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z\\\" /><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"3\\\" />\"\n};\n\n // Global icon registry\n window.RailsPulseIcons = {\n icons: icons,\n\n // Get icon SVG content\n get: function(name) {\n return icons[name] || null;\n },\n\n // Check if icon exists\n has: function(name) {\n return name in icons;\n },\n\n // Get all icon names\n list: function() {\n return Object.keys(icons);\n },\n\n // Render icon to element (CSP-safe)\n render: function(name, element, options = {}) {\n const svgContent = this.get(name);\n if (!svgContent || !element) return false;\n\n // Create SVG element\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', options.width || '24');\n svg.setAttribute('height', options.height || '24');\n svg.setAttribute('viewBox', '0 0 24 24');\n svg.setAttribute('fill', 'none');\n svg.setAttribute('stroke', 'currentColor');\n svg.setAttribute('stroke-width', '2');\n svg.setAttribute('stroke-linecap', 'round');\n svg.setAttribute('stroke-linejoin', 'round');\n\n // Add icon content\n svg.innerHTML = svgContent;\n\n // Replace element content\n element.innerHTML = '';\n element.appendChild(svg);\n\n return true;\n }\n };\n})();\n"
11
+ "// Rails Pulse Icons Bundle - Auto-generated\n// Contains 34 SVG icons for Rails Pulse\n\n(function() {\n 'use strict';\n\n // Icon data\n const icons = {\n \"menu\": \"<path d=\\\"M4 5h16\\\" /><path d=\\\"M4 12h16\\\" /><path d=\\\"M4 19h16\\\" />\",\n \"sun\": \"<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"4\\\" /><path d=\\\"M12 2v2\\\" /><path d=\\\"M12 20v2\\\" /><path d=\\\"m4.93 4.93 1.41 1.41\\\" /><path d=\\\"m17.66 17.66 1.41 1.41\\\" /><path d=\\\"M2 12h2\\\" /><path d=\\\"M20 12h2\\\" /><path d=\\\"m6.34 17.66-1.41 1.41\\\" /><path d=\\\"m19.07 4.93-1.41 1.41\\\" />\",\n \"moon\": \"<path d=\\\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\\\" />\",\n \"chevron-right\": \"<path d=\\\"m9 18 6-6-6-6\\\" />\",\n \"chevron-left\": \"<path d=\\\"m15 18-6-6 6-6\\\" />\",\n \"chevron-down\": \"<path d=\\\"m6 9 6 6 6-6\\\" />\",\n \"chevron-up\": \"<path d=\\\"m18 15-6-6-6 6\\\" />\",\n \"chevrons-left\": \"<path d=\\\"m11 17-5-5 5-5\\\" /><path d=\\\"m18 17-5-5 5-5\\\" />\",\n \"chevrons-right\": \"<path d=\\\"m6 17 5-5-5-5\\\" /><path d=\\\"m13 17 5-5-5-5\\\" />\",\n \"loader-circle\": \"<path d=\\\"M12 2v4\\\" /><path d=\\\"m16.2 7.8 2.9-2.9\\\" /><path d=\\\"M18 12h4\\\" /><path d=\\\"m16.2 16.2 2.9 2.9\\\" /><path d=\\\"M12 18v4\\\" /><path d=\\\"m4.9 19.1 2.9-2.9\\\" /><path d=\\\"M2 12h4\\\" /><path d=\\\"m4.9 4.9 2.9 2.9\\\" />\",\n \"search\": \"<path d=\\\"m21 21-4.34-4.34\\\" /><circle cx=\\\"11\\\" cy=\\\"11\\\" r=\\\"8\\\" />\",\n \"list-filter\": \"<path d=\\\"M2 5h20\\\" /><path d=\\\"M6 12h12\\\" /><path d=\\\"M9 19h6\\\" />\",\n \"list-filter-plus\": \"<path d=\\\"M12 5H2\\\" /><path d=\\\"M6 12h12\\\" /><path d=\\\"M9 19h6\\\" /><path d=\\\"M16 5h6\\\" /><path d=\\\"M19 8V2\\\" />\",\n \"x\": \"<path d=\\\"M18 6 6 18\\\" /><path d=\\\"m6 6 12 12\\\" />\",\n \"x-circle\": \"<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\" /><path d=\\\"m15 9-6 6\\\" /><path d=\\\"m9 9 6 6\\\" />\",\n \"check\": \"<path d=\\\"M20 6 9 17l-5-5\\\" />\",\n \"alert-circle\": \"<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\" /><line x1=\\\"12\\\" x2=\\\"12\\\" y1=\\\"8\\\" y2=\\\"12\\\" /><line x1=\\\"12\\\" x2=\\\"12.01\\\" y1=\\\"16\\\" y2=\\\"16\\\" />\",\n \"alert-triangle\": \"<path d=\\\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\\\" /><path d=\\\"M12 9v4\\\" /><path d=\\\"M12 17h.01\\\" />\",\n \"info\": \"<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\" /><path d=\\\"M12 16v-4\\\" /><path d=\\\"M12 8h.01\\\" />\",\n \"external-link\": \"<path d=\\\"M15 3h6v6\\\" /><path d=\\\"M10 14 21 3\\\" /><path d=\\\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\\\" />\",\n \"download\": \"<path d=\\\"M12 15V3\\\" /><path d=\\\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\\\" /><path d=\\\"m7 10 5 5 5-5\\\" />\",\n \"refresh-cw\": \"<path d=\\\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\\\" /><path d=\\\"M21 3v5h-5\\\" /><path d=\\\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\\\" /><path d=\\\"M8 16H3v5\\\" />\",\n \"clock\": \"<path d=\\\"M12 6v6l4 2\\\" /><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\" />\",\n \"database\": \"<ellipse cx=\\\"12\\\" cy=\\\"5\\\" rx=\\\"9\\\" ry=\\\"3\\\" /><path d=\\\"M3 5V19A9 3 0 0 0 21 19V5\\\" /><path d=\\\"M3 12A9 3 0 0 0 21 12\\\" />\",\n \"server\": \"<rect width=\\\"20\\\" height=\\\"8\\\" x=\\\"2\\\" y=\\\"2\\\" rx=\\\"2\\\" ry=\\\"2\\\" /><rect width=\\\"20\\\" height=\\\"8\\\" x=\\\"2\\\" y=\\\"14\\\" rx=\\\"2\\\" ry=\\\"2\\\" /><line x1=\\\"6\\\" x2=\\\"6.01\\\" y1=\\\"6\\\" y2=\\\"6\\\" /><line x1=\\\"6\\\" x2=\\\"6.01\\\" y1=\\\"18\\\" y2=\\\"18\\\" />\",\n \"activity\": \"<path d=\\\"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2\\\" />\",\n \"layout-dashboard\": \"<rect width=\\\"7\\\" height=\\\"9\\\" x=\\\"3\\\" y=\\\"3\\\" rx=\\\"1\\\" /><rect width=\\\"7\\\" height=\\\"5\\\" x=\\\"14\\\" y=\\\"3\\\" rx=\\\"1\\\" /><rect width=\\\"7\\\" height=\\\"9\\\" x=\\\"14\\\" y=\\\"12\\\" rx=\\\"1\\\" /><rect width=\\\"7\\\" height=\\\"5\\\" x=\\\"3\\\" y=\\\"16\\\" rx=\\\"1\\\" />\",\n \"audio-lines\": \"<path d=\\\"M2 10v3\\\" /><path d=\\\"M6 6v11\\\" /><path d=\\\"M10 3v18\\\" /><path d=\\\"M14 8v7\\\" /><path d=\\\"M18 5v13\\\" /><path d=\\\"M22 10v3\\\" />\",\n \"message-circle-question\": \"<path d=\\\"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719\\\" /><path d=\\\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\\\" /><path d=\\\"M12 17h.01\\\" />\",\n \"route\": \"<circle cx=\\\"6\\\" cy=\\\"19\\\" r=\\\"3\\\" /><path d=\\\"M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15\\\" /><circle cx=\\\"18\\\" cy=\\\"5\\\" r=\\\"3\\\" />\",\n \"trending-up\": \"<path d=\\\"M16 7h6v6\\\" /><path d=\\\"m22 7-8.5 8.5-5-5L2 17\\\" />\",\n \"trending-down\": \"<path d=\\\"M16 17h6v-6\\\" /><path d=\\\"m22 17-8.5-8.5-5 5L2 7\\\" />\",\n \"move-right\": \"<path d=\\\"M18 8L22 12L18 16\\\" /><path d=\\\"M2 12H22\\\" />\",\n \"eye\": \"<path d=\\\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\\\" /><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"3\\\" />\"\n};\n\n // Global icon registry\n window.RailsPulseIcons = {\n icons: icons,\n\n // Get icon SVG content\n get: function(name) {\n return icons[name] || null;\n },\n\n // Check if icon exists\n has: function(name) {\n return name in icons;\n },\n\n // Get all icon names\n list: function() {\n return Object.keys(icons);\n },\n\n // Render icon to element (CSP-safe)\n render: function(name, element, options = {}) {\n const svgContent = this.get(name);\n if (!svgContent || !element) return false;\n\n // Create SVG element\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', options.width || '24');\n svg.setAttribute('height', options.height || '24');\n svg.setAttribute('viewBox', '0 0 24 24');\n svg.setAttribute('fill', 'none');\n svg.setAttribute('stroke', 'currentColor');\n svg.setAttribute('stroke-width', '2');\n svg.setAttribute('stroke-linecap', 'round');\n svg.setAttribute('stroke-linejoin', 'round');\n\n // Add icon content\n svg.innerHTML = svgContent;\n\n // Replace element content\n element.innerHTML = '';\n element.appendChild(svg);\n\n return true;\n }\n };\n})();\n"
12
12
  ]
13
13
  }