source_monitor 0.2.0 → 0.3.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 (196) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/agents/rails-concern.md +464 -0
  3. data/.claude/agents/rails-controller.md +424 -0
  4. data/.claude/agents/rails-hotwire.md +446 -0
  5. data/.claude/agents/rails-implement.md +374 -0
  6. data/.claude/agents/rails-job.md +334 -0
  7. data/.claude/agents/rails-lint.md +294 -0
  8. data/.claude/agents/rails-mailer.md +371 -0
  9. data/.claude/agents/rails-migration.md +449 -0
  10. data/.claude/agents/rails-model.md +420 -0
  11. data/.claude/agents/rails-policy.md +443 -0
  12. data/.claude/agents/rails-presenter.md +427 -0
  13. data/.claude/agents/rails-query.md +412 -0
  14. data/.claude/agents/rails-review.md +490 -0
  15. data/.claude/agents/rails-service.md +458 -0
  16. data/.claude/agents/rails-state-records.md +465 -0
  17. data/.claude/agents/rails-tdd.md +314 -0
  18. data/.claude/agents/rails-test.md +441 -0
  19. data/.claude/agents/rails-view-component.md +418 -0
  20. data/.claude/hooks/block-secrets.sh +52 -0
  21. data/.claude/settings.json +85 -0
  22. data/.claude/skills/action-cable-patterns/SKILL.md +296 -0
  23. data/.claude/skills/action-mailer-patterns/SKILL.md +295 -0
  24. data/.claude/skills/active-storage-setup/SKILL.md +311 -0
  25. data/.claude/skills/api-versioning/SKILL.md +294 -0
  26. data/.claude/skills/authentication-flow/SKILL.md +335 -0
  27. data/.claude/skills/authentication-flow/reference/current.md +248 -0
  28. data/.claude/skills/authentication-flow/reference/passwordless.md +253 -0
  29. data/.claude/skills/authentication-flow/reference/sessions.md +201 -0
  30. data/.claude/skills/authorization-pundit/SKILL.md +462 -0
  31. data/.claude/skills/caching-strategies/SKILL.md +350 -0
  32. data/.claude/skills/database-migrations/SKILL.md +354 -0
  33. data/.claude/skills/form-object-patterns/SKILL.md +399 -0
  34. data/.claude/skills/hotwire-patterns/SKILL.md +247 -0
  35. data/.claude/skills/hotwire-patterns/reference/stimulus.md +307 -0
  36. data/.claude/skills/hotwire-patterns/reference/tailwind-integration.md +112 -0
  37. data/.claude/skills/hotwire-patterns/reference/turbo-frames.md +158 -0
  38. data/.claude/skills/hotwire-patterns/reference/turbo-streams.md +218 -0
  39. data/.claude/skills/i18n-patterns/SKILL.md +320 -0
  40. data/.claude/skills/install/SKILL.md +367 -0
  41. data/.claude/skills/performance-optimization/SKILL.md +311 -0
  42. data/.claude/skills/rails-architecture/SKILL.md +259 -0
  43. data/.claude/skills/rails-architecture/reference/error-handling.md +333 -0
  44. data/.claude/skills/rails-architecture/reference/event-tracking.md +142 -0
  45. data/.claude/skills/rails-architecture/reference/layer-interactions.md +417 -0
  46. data/.claude/skills/rails-architecture/reference/multi-tenancy.md +152 -0
  47. data/.claude/skills/rails-architecture/reference/query-patterns.md +342 -0
  48. data/.claude/skills/rails-architecture/reference/service-patterns.md +286 -0
  49. data/.claude/skills/rails-architecture/reference/state-records.md +250 -0
  50. data/.claude/skills/rails-architecture/reference/testing-strategy.md +326 -0
  51. data/.claude/skills/rails-concern/SKILL.md +399 -0
  52. data/.claude/skills/rails-controller/SKILL.md +336 -0
  53. data/.claude/skills/rails-model-generator/SKILL.md +321 -0
  54. data/.claude/skills/rails-model-generator/reference/validations.md +298 -0
  55. data/.claude/skills/rails-presenter/SKILL.md +274 -0
  56. data/.claude/skills/rails-query-object/SKILL.md +289 -0
  57. data/.claude/skills/rails-service-object/SKILL.md +349 -0
  58. data/.claude/skills/solid-queue-setup/SKILL.md +307 -0
  59. data/.claude/skills/tdd-cycle/SKILL.md +359 -0
  60. data/.claude/skills/viewcomponent-patterns/SKILL.md +333 -0
  61. data/.gitignore +1 -0
  62. data/.rubocop.yml +2 -0
  63. data/.ruby-version +1 -1
  64. data/.vbw-planning/.notification-log.jsonl +192 -0
  65. data/.vbw-planning/.session-log.jsonl +871 -0
  66. data/.vbw-planning/PROJECT.md +51 -0
  67. data/.vbw-planning/REQUIREMENTS.md +50 -0
  68. data/.vbw-planning/SHIPPED.md +28 -0
  69. data/.vbw-planning/codebase/ARCHITECTURE.md +147 -0
  70. data/.vbw-planning/codebase/CONCERNS.md +99 -0
  71. data/.vbw-planning/codebase/CONVENTIONS.md +97 -0
  72. data/.vbw-planning/codebase/DEPENDENCIES.md +100 -0
  73. data/.vbw-planning/codebase/INDEX.md +86 -0
  74. data/.vbw-planning/codebase/META.md +42 -0
  75. data/.vbw-planning/codebase/PATTERNS.md +262 -0
  76. data/.vbw-planning/codebase/STACK.md +101 -0
  77. data/.vbw-planning/codebase/STRUCTURE.md +324 -0
  78. data/.vbw-planning/codebase/TESTING.md +154 -0
  79. data/.vbw-planning/config.json +12 -0
  80. data/.vbw-planning/discovery.json +24 -0
  81. data/.vbw-planning/milestones/default/ROADMAP.md +115 -0
  82. data/.vbw-planning/milestones/default/STATE.md +83 -0
  83. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01-SUMMARY.md +56 -0
  84. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01.md +187 -0
  85. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02-SUMMARY.md +64 -0
  86. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02.md +137 -0
  87. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01-SUMMARY.md +67 -0
  88. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01.md +142 -0
  89. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02-SUMMARY.md +64 -0
  90. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02.md +138 -0
  91. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03-SUMMARY.md +85 -0
  92. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03.md +147 -0
  93. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04-SUMMARY.md +63 -0
  94. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04.md +129 -0
  95. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05-SUMMARY.md +74 -0
  96. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05.md +154 -0
  97. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION-wave1.md +303 -0
  98. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION.md +510 -0
  99. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01-SUMMARY.md +61 -0
  100. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01.md +161 -0
  101. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02-SUMMARY.md +66 -0
  102. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02.md +132 -0
  103. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03-SUMMARY.md +59 -0
  104. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03.md +171 -0
  105. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04-SUMMARY.md +56 -0
  106. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04.md +152 -0
  107. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/04-CONTEXT.md +33 -0
  108. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01-SUMMARY.md +42 -0
  109. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01.md +119 -0
  110. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02-SUMMARY.md +52 -0
  111. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02.md +195 -0
  112. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03-SUMMARY.md +79 -0
  113. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03.md +130 -0
  114. data/CHANGELOG.md +28 -0
  115. data/CLAUDE.md +179 -0
  116. data/Gemfile +8 -0
  117. data/Gemfile.lock +114 -101
  118. data/Rakefile +2 -0
  119. data/app/assets/builds/source_monitor/application.css +2076 -0
  120. data/app/assets/builds/source_monitor/application.js +2758 -0
  121. data/app/assets/builds/source_monitor/application.js.map +7 -0
  122. data/app/controllers/source_monitor/application_controller.rb +2 -0
  123. data/app/controllers/source_monitor/health_controller.rb +2 -0
  124. data/app/controllers/source_monitor/import_sessions/bulk_configuration.rb +106 -0
  125. data/app/controllers/source_monitor/import_sessions/entry_annotation.rb +187 -0
  126. data/app/controllers/source_monitor/import_sessions/health_check_management.rb +112 -0
  127. data/app/controllers/source_monitor/import_sessions/opml_parser.rb +130 -0
  128. data/app/controllers/source_monitor/import_sessions_controller.rb +6 -507
  129. data/app/controllers/source_monitor/items_controller.rb +2 -0
  130. data/app/controllers/source_monitor/sources_controller.rb +0 -14
  131. data/app/helpers/source_monitor/application_helper.rb +4 -112
  132. data/app/helpers/source_monitor/health_badge_helper.rb +69 -0
  133. data/app/helpers/source_monitor/table_sort_helper.rb +53 -0
  134. data/app/jobs/source_monitor/application_job.rb +2 -0
  135. data/app/models/source_monitor/application_record.rb +2 -0
  136. data/app/models/source_monitor/log_entry.rb +0 -2
  137. data/config/coverage_baseline.json +217 -1862
  138. data/config/routes.rb +2 -0
  139. data/db/migrate/20251009103000_add_feed_content_readability_to_sources.rb +2 -0
  140. data/db/migrate/20251014171659_add_performance_indexes.rb +2 -0
  141. data/db/migrate/20251014172525_add_fetch_status_check_constraint.rb +2 -0
  142. data/db/migrate/20251108120116_refresh_fetch_status_constraint.rb +2 -0
  143. data/db/migrate/20260210204022_add_composite_index_to_log_entries.rb +17 -0
  144. data/lib/source_monitor/assets/bundler.rb +2 -0
  145. data/lib/source_monitor/assets.rb +2 -0
  146. data/lib/source_monitor/configuration/authentication_settings.rb +62 -0
  147. data/lib/source_monitor/configuration/events.rb +60 -0
  148. data/lib/source_monitor/configuration/fetching_settings.rb +27 -0
  149. data/lib/source_monitor/configuration/health_settings.rb +27 -0
  150. data/lib/source_monitor/configuration/http_settings.rb +43 -0
  151. data/lib/source_monitor/configuration/model_definition.rb +108 -0
  152. data/lib/source_monitor/configuration/models.rb +36 -0
  153. data/lib/source_monitor/configuration/realtime_settings.rb +95 -0
  154. data/lib/source_monitor/configuration/retention_settings.rb +45 -0
  155. data/lib/source_monitor/configuration/scraper_registry.rb +67 -0
  156. data/lib/source_monitor/configuration/scraping_settings.rb +39 -0
  157. data/lib/source_monitor/configuration/validation_definition.rb +32 -0
  158. data/lib/source_monitor/configuration.rb +12 -579
  159. data/lib/source_monitor/dashboard/queries/recent_activity_query.rb +138 -0
  160. data/lib/source_monitor/dashboard/queries/stats_query.rb +71 -0
  161. data/lib/source_monitor/dashboard/queries.rb +2 -195
  162. data/lib/source_monitor/engine.rb +2 -0
  163. data/lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb +141 -0
  164. data/lib/source_monitor/fetching/feed_fetcher/entry_processor.rb +89 -0
  165. data/lib/source_monitor/fetching/feed_fetcher/source_updater.rb +200 -0
  166. data/lib/source_monitor/fetching/feed_fetcher.rb +37 -379
  167. data/lib/source_monitor/items/item_creator/content_extractor.rb +113 -0
  168. data/lib/source_monitor/items/item_creator/entry_parser/media_extraction.rb +96 -0
  169. data/lib/source_monitor/items/item_creator/entry_parser.rb +294 -0
  170. data/lib/source_monitor/items/item_creator.rb +28 -455
  171. data/lib/source_monitor/setup/bundle_installer.rb +2 -0
  172. data/lib/source_monitor/setup/cli.rb +2 -0
  173. data/lib/source_monitor/setup/dependency_checker.rb +2 -0
  174. data/lib/source_monitor/setup/detectors.rb +2 -0
  175. data/lib/source_monitor/setup/gemfile_editor.rb +2 -0
  176. data/lib/source_monitor/setup/initializer_patcher.rb +2 -0
  177. data/lib/source_monitor/setup/install_generator.rb +2 -0
  178. data/lib/source_monitor/setup/migration_installer.rb +2 -0
  179. data/lib/source_monitor/setup/node_installer.rb +2 -0
  180. data/lib/source_monitor/setup/prompter.rb +2 -0
  181. data/lib/source_monitor/setup/requirements.rb +2 -0
  182. data/lib/source_monitor/setup/shell_runner.rb +2 -0
  183. data/lib/source_monitor/setup/verification/action_cable_verifier.rb +2 -0
  184. data/lib/source_monitor/setup/verification/printer.rb +2 -0
  185. data/lib/source_monitor/setup/verification/result.rb +2 -0
  186. data/lib/source_monitor/setup/verification/runner.rb +2 -0
  187. data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +2 -0
  188. data/lib/source_monitor/setup/verification/telemetry_logger.rb +2 -0
  189. data/lib/source_monitor/setup/workflow.rb +2 -0
  190. data/lib/source_monitor/version.rb +3 -1
  191. data/lib/source_monitor.rb +140 -58
  192. data/lib/tasks/source_monitor_assets.rake +2 -0
  193. data/lib/tasks/source_monitor_setup.rake +2 -0
  194. data/lib/tasks/source_monitor_tasks.rake +2 -0
  195. data/source_monitor.gemspec +3 -1
  196. metadata +144 -4
@@ -0,0 +1,307 @@
1
+ # Stimulus Reference
2
+
3
+ ## Concept
4
+
5
+ Stimulus is a modest JavaScript framework for adding behavior to HTML. It connects JavaScript objects (controllers) to DOM elements using data attributes.
6
+
7
+ ## Core Concepts
8
+
9
+ | Concept | Purpose | Attribute |
10
+ |---------|---------|-----------|
11
+ | Controller | JavaScript class | `data-controller="name"` |
12
+ | Action | Event handler | `data-action="event->controller#method"` |
13
+ | Target | DOM reference | `data-controller-target="name"` |
14
+ | Value | Reactive data | `data-controller-name-value="x"` |
15
+ | Class | CSS class reference | `data-controller-name-class="x"` |
16
+ | Outlet | Cross-controller reference | `data-controller-name-outlet=".selector"` |
17
+
18
+ ## Basic Controller
19
+
20
+ ```javascript
21
+ // app/javascript/controllers/hello_controller.js
22
+ import { Controller } from "@hotwired/stimulus"
23
+
24
+ export default class extends Controller {
25
+ static targets = ["output"]
26
+
27
+ greet() {
28
+ this.outputTarget.textContent = "Hello, Stimulus!"
29
+ }
30
+ }
31
+ ```
32
+
33
+ ```erb
34
+ <div data-controller="hello">
35
+ <button data-action="click->hello#greet">Greet</button>
36
+ <span data-hello-target="output"></span>
37
+ </div>
38
+ ```
39
+
40
+ ## Controller Lifecycle
41
+
42
+ ```javascript
43
+ import { Controller } from "@hotwired/stimulus"
44
+
45
+ export default class extends Controller {
46
+ // Called when controller connects to DOM
47
+ connect() {
48
+ console.log("Connected!", this.element)
49
+ }
50
+
51
+ // Called when controller disconnects
52
+ disconnect() {
53
+ console.log("Disconnected!")
54
+ }
55
+
56
+ // Called when target is added
57
+ outputTargetConnected(element) {
58
+ console.log("Target connected:", element)
59
+ }
60
+
61
+ // Called when target is removed
62
+ outputTargetDisconnected(element) {
63
+ console.log("Target disconnected:", element)
64
+ }
65
+ }
66
+ ```
67
+
68
+ ## Targets
69
+
70
+ ```javascript
71
+ import { Controller } from "@hotwired/stimulus"
72
+
73
+ export default class extends Controller {
74
+ static targets = ["input", "output", "item"]
75
+
76
+ // Single target (first match)
77
+ copy() {
78
+ this.outputTarget.textContent = this.inputTarget.value
79
+ }
80
+
81
+ // Multiple targets (all matches)
82
+ clearAll() {
83
+ this.itemTargets.forEach(el => el.remove())
84
+ }
85
+
86
+ // Check if target exists
87
+ validate() {
88
+ if (this.hasOutputTarget) {
89
+ this.outputTarget.classList.add("validated")
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ```erb
96
+ <div data-controller="form">
97
+ <input data-form-target="input" type="text">
98
+ <div data-form-target="output"></div>
99
+ <div data-form-target="item">Item 1</div>
100
+ <div data-form-target="item">Item 2</div>
101
+ </div>
102
+ ```
103
+
104
+ ## Values
105
+
106
+ ```javascript
107
+ import { Controller } from "@hotwired/stimulus"
108
+
109
+ export default class extends Controller {
110
+ static values = {
111
+ url: String,
112
+ count: { type: Number, default: 0 },
113
+ enabled: Boolean,
114
+ config: Object,
115
+ items: Array
116
+ }
117
+
118
+ connect() {
119
+ console.log(this.urlValue) // String
120
+ console.log(this.countValue) // Number
121
+ console.log(this.enabledValue) // Boolean
122
+ }
123
+
124
+ // Called when value changes
125
+ countValueChanged(value, previousValue) {
126
+ console.log(`Count changed from ${previousValue} to ${value}`)
127
+ }
128
+
129
+ increment() {
130
+ this.countValue++ // Triggers countValueChanged
131
+ }
132
+ }
133
+ ```
134
+
135
+ ```erb
136
+ <div data-controller="counter"
137
+ data-counter-url-value="/api/count"
138
+ data-counter-count-value="5"
139
+ data-counter-enabled-value="true"
140
+ data-counter-config-value='{"max": 100}'>
141
+ </div>
142
+ ```
143
+
144
+ ## Actions
145
+
146
+ ```javascript
147
+ import { Controller } from "@hotwired/stimulus"
148
+
149
+ export default class extends Controller {
150
+ // Basic action
151
+ submit() {
152
+ console.log("Submitted!")
153
+ }
154
+
155
+ // With event parameter
156
+ handleClick(event) {
157
+ event.preventDefault()
158
+ console.log("Clicked:", event.target)
159
+ }
160
+
161
+ // With params
162
+ delete(event) {
163
+ const id = event.params.id
164
+ console.log("Delete item:", id)
165
+ }
166
+ }
167
+ ```
168
+
169
+ ```erb
170
+ <div data-controller="items">
171
+ <%# Basic action %>
172
+ <button data-action="click->items#submit">Submit</button>
173
+
174
+ <%# Multiple events %>
175
+ <input data-action="input->items#validate focus->items#highlight">
176
+
177
+ <%# Shorthand (click is default for buttons) %>
178
+ <button data-action="items#submit">Submit</button>
179
+
180
+ <%# With params %>
181
+ <button data-action="items#delete" data-items-id-param="123">Delete</button>
182
+
183
+ <%# Prevent default %>
184
+ <form data-action="submit->items#handleSubmit:prevent">
185
+
186
+ <%# Stop propagation %>
187
+ <button data-action="click->items#handle:stop">Click</button>
188
+ </div>
189
+ ```
190
+
191
+ ## Common Patterns
192
+
193
+ ### Toggle Visibility
194
+
195
+ ```javascript
196
+ // toggle_controller.js
197
+ import { Controller } from "@hotwired/stimulus"
198
+
199
+ export default class extends Controller {
200
+ static targets = ["content"]
201
+ static classes = ["hidden"]
202
+
203
+ toggle() {
204
+ this.contentTarget.classList.toggle(this.hiddenClass)
205
+ }
206
+
207
+ show() {
208
+ this.contentTarget.classList.remove(this.hiddenClass)
209
+ }
210
+
211
+ hide() {
212
+ this.contentTarget.classList.add(this.hiddenClass)
213
+ }
214
+ }
215
+ ```
216
+
217
+ ```erb
218
+ <div data-controller="toggle" data-toggle-hidden-class="hidden">
219
+ <button data-action="toggle#toggle">Toggle</button>
220
+ <div data-toggle-target="content">Content here</div>
221
+ </div>
222
+ ```
223
+
224
+ ### Form Validation
225
+
226
+ ```javascript
227
+ // validation_controller.js
228
+ import { Controller } from "@hotwired/stimulus"
229
+
230
+ export default class extends Controller {
231
+ static targets = ["input", "error", "submit"]
232
+
233
+ validate() {
234
+ const isValid = this.inputTarget.value.length >= 3
235
+
236
+ this.errorTarget.textContent = isValid ? "" : "Minimum 3 characters"
237
+ this.submitTarget.disabled = !isValid
238
+ }
239
+ }
240
+ ```
241
+
242
+ ### Debounced Search
243
+
244
+ ```javascript
245
+ // search_controller.js
246
+ import { Controller } from "@hotwired/stimulus"
247
+
248
+ export default class extends Controller {
249
+ static targets = ["input", "results"]
250
+ static values = { url: String }
251
+
252
+ search() {
253
+ clearTimeout(this.timeout)
254
+ this.timeout = setTimeout(() => {
255
+ this.performSearch()
256
+ }, 300)
257
+ }
258
+
259
+ async performSearch() {
260
+ const query = this.inputTarget.value
261
+ const response = await fetch(`${this.urlValue}?q=${query}`)
262
+ this.resultsTarget.innerHTML = await response.text()
263
+ }
264
+ }
265
+ ```
266
+
267
+ ### Clipboard
268
+
269
+ ```javascript
270
+ // clipboard_controller.js
271
+ import { Controller } from "@hotwired/stimulus"
272
+
273
+ export default class extends Controller {
274
+ static targets = ["source"]
275
+ static values = { successMessage: { type: String, default: "Copied!" } }
276
+
277
+ copy() {
278
+ navigator.clipboard.writeText(this.sourceTarget.value)
279
+ this.showNotification()
280
+ }
281
+
282
+ showNotification() {
283
+ // Show temporary feedback
284
+ }
285
+ }
286
+ ```
287
+
288
+ ## File Naming
289
+
290
+ ```
291
+ app/javascript/controllers/
292
+ ├── application.js # Auto-generated
293
+ ├── index.js # Auto-generated
294
+ ├── hello_controller.js # data-controller="hello"
295
+ ├── clipboard_controller.js # data-controller="clipboard"
296
+ └── nested/
297
+ └── form_controller.js # data-controller="nested--form"
298
+ ```
299
+
300
+ ## Debugging
301
+
302
+ ```javascript
303
+ // Enable debug mode
304
+ import { Application } from "@hotwired/stimulus"
305
+ const application = Application.start()
306
+ application.debug = true // Logs controller lifecycle
307
+ ```
@@ -0,0 +1,112 @@
1
+ # Tailwind CSS Integration with Hotwire
2
+
3
+ ## Principles
4
+
5
+ - Mobile-first responsive design
6
+ - Semantic HTML with accessibility
7
+ - Consistent color palette and spacing
8
+ - Focus states on all interactive elements
9
+
10
+ ## Responsive Breakpoints
11
+
12
+ ```
13
+ sm: 640px+ (small tablets)
14
+ md: 768px+ (tablets)
15
+ lg: 1024px+ (desktops)
16
+ xl: 1280px+ (large desktops)
17
+ ```
18
+
19
+ ## Common Patterns
20
+
21
+ ### Responsive Grid
22
+
23
+ ```erb
24
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
25
+ <%= render @items %>
26
+ </div>
27
+ ```
28
+
29
+ ### Button Variants
30
+
31
+ ```erb
32
+ <%# Primary %>
33
+ <%= link_to "Save", path, class: "bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors" %>
34
+
35
+ <%# Secondary %>
36
+ <%= link_to "Cancel", path, class: "bg-gray-100 hover:bg-gray-200 text-gray-700 font-semibold py-2 px-4 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors" %>
37
+
38
+ <%# Danger %>
39
+ <%= button_to "Delete", path, method: :delete, data: { turbo_confirm: "Are you sure?" }, class: "bg-red-600 hover:bg-red-700 text-white font-semibold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition-colors" %>
40
+ ```
41
+
42
+ ### Form Fields
43
+
44
+ ```erb
45
+ <div class="space-y-1">
46
+ <%= f.label :name, class: "block text-sm font-medium text-gray-700" %>
47
+ <%= f.text_field :name, class: "w-full px-3 py-2 rounded-md border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 transition-colors", placeholder: "Enter name..." %>
48
+ </div>
49
+ ```
50
+
51
+ ### Cards
52
+
53
+ ```erb
54
+ <div class="bg-white rounded-lg shadow-md p-6">
55
+ <h3 class="text-xl font-semibold text-gray-800 mb-2">Title</h3>
56
+ <p class="text-gray-600">Content</p>
57
+ </div>
58
+ ```
59
+
60
+ ### Badges
61
+
62
+ ```erb
63
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">Active</span>
64
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">Inactive</span>
65
+ ```
66
+
67
+ ### Alerts
68
+
69
+ ```erb
70
+ <div class="bg-green-50 border border-green-200 text-green-800 px-4 py-3 rounded-md" role="alert">
71
+ <p class="font-medium">Success!</p>
72
+ </div>
73
+ ```
74
+
75
+ ## Turbo-Specific Styling
76
+
77
+ ### Turbo Frame Loading State
78
+
79
+ ```erb
80
+ <turbo-frame id="comments" src="<%= comments_path %>" loading="lazy" class="space-y-4">
81
+ <div class="flex items-center justify-center p-8">
82
+ <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
83
+ </div>
84
+ </turbo-frame>
85
+ ```
86
+
87
+ ### Skeleton Loader
88
+
89
+ ```erb
90
+ <div class="animate-pulse space-y-4">
91
+ <div class="h-4 bg-gray-200 rounded w-3/4"></div>
92
+ <div class="h-4 bg-gray-200 rounded w-1/2"></div>
93
+ </div>
94
+ ```
95
+
96
+ ## Accessibility
97
+
98
+ - Use semantic HTML (`<nav>`, `<main>`, `<article>`, `<button>`)
99
+ - Include `aria-label` for icon-only buttons
100
+ - Ensure focus states with `focus:ring-` classes
101
+ - Add `sr-only` class for screen-reader-only text
102
+ - Minimum contrast ratio WCAG AA: 4.5:1
103
+
104
+ ## Color Usage
105
+
106
+ | Color | Purpose |
107
+ |-------|---------|
108
+ | `blue-*` | Primary actions, links |
109
+ | `green-*` | Success, confirmations |
110
+ | `red-*` | Errors, destructive actions |
111
+ | `yellow-*` | Warnings |
112
+ | `gray-*` | Neutral, borders, disabled |
@@ -0,0 +1,158 @@
1
+ # Turbo Frames Reference
2
+
3
+ ## Concept
4
+
5
+ Turbo Frames scope navigation to a portion of the page. When a link or form inside a frame is activated, only that frame's content is replaced.
6
+
7
+ ## Basic Usage
8
+
9
+ ### Define a Frame
10
+
11
+ ```erb
12
+ <%= turbo_frame_tag "user_profile" do %>
13
+ <h2><%= @user.name %></h2>
14
+ <%= link_to "Edit", edit_user_path(@user) %>
15
+ <% end %>
16
+ ```
17
+
18
+ ### Match Frame in Response
19
+
20
+ ```erb
21
+ <%# edit.html.erb - must have matching frame %>
22
+ <%= turbo_frame_tag "user_profile" do %>
23
+ <%= form_with model: @user do |f| %>
24
+ <%= f.text_field :name %>
25
+ <%= f.submit %>
26
+ <% end %>
27
+ <% end %>
28
+ ```
29
+
30
+ ## Frame Attributes
31
+
32
+ | Attribute | Purpose | Example |
33
+ |-----------|---------|---------|
34
+ | `id` | Frame identifier | `turbo_frame_tag "posts"` |
35
+ | `src` | Lazy load URL | `src: posts_path` |
36
+ | `loading` | Load timing | `loading: :lazy` |
37
+ | `target` | Navigation target | `target: "_top"` |
38
+ | `disabled` | Disable frame | `disabled: true` |
39
+
40
+ ## Common Patterns
41
+
42
+ ### Lazy Loading
43
+
44
+ ```erb
45
+ <%# Load content when frame enters viewport %>
46
+ <%= turbo_frame_tag "comments",
47
+ src: post_comments_path(@post),
48
+ loading: :lazy do %>
49
+ <p>Loading comments...</p>
50
+ <% end %>
51
+ ```
52
+
53
+ ### Breaking Out of Frame
54
+
55
+ ```erb
56
+ <%# Link navigates full page, not just frame %>
57
+ <%= link_to "View All", posts_path, data: { turbo_frame: "_top" } %>
58
+
59
+ <%# Or in the frame tag %>
60
+ <%= turbo_frame_tag "modal", target: "_top" do %>
61
+ ...
62
+ <% end %>
63
+ ```
64
+
65
+ ### Targeting Different Frame
66
+
67
+ ```erb
68
+ <%# Link updates a different frame %>
69
+ <%= link_to "Details", post_path(@post), data: { turbo_frame: "post_details" } %>
70
+
71
+ <%# This frame will be updated %>
72
+ <%= turbo_frame_tag "post_details" do %>
73
+ <p>Select a post to see details</p>
74
+ <% end %>
75
+ ```
76
+
77
+ ### Inline Editing Pattern
78
+
79
+ ```erb
80
+ <%# Show mode %>
81
+ <%= turbo_frame_tag dom_id(post) do %>
82
+ <div class="post">
83
+ <h3><%= post.title %></h3>
84
+ <p><%= post.body %></p>
85
+ <%= link_to "Edit", edit_post_path(post) %>
86
+ </div>
87
+ <% end %>
88
+
89
+ <%# Edit mode (edit.html.erb) %>
90
+ <%= turbo_frame_tag dom_id(@post) do %>
91
+ <%= form_with model: @post, data: { turbo_frame: dom_id(@post) } do |f| %>
92
+ <%= f.text_field :title %>
93
+ <%= f.text_area :body %>
94
+ <%= f.submit "Save" %>
95
+ <%= link_to "Cancel", @post %>
96
+ <% end %>
97
+ <% end %>
98
+ ```
99
+
100
+ ### Modal Pattern
101
+
102
+ ```erb
103
+ <%# Trigger link %>
104
+ <%= link_to "New Post", new_post_path, data: { turbo_frame: "modal" } %>
105
+
106
+ <%# Modal frame (in layout) %>
107
+ <%= turbo_frame_tag "modal" %>
108
+
109
+ <%# new.html.erb %>
110
+ <%= turbo_frame_tag "modal" do %>
111
+ <div class="modal-backdrop">
112
+ <div class="modal-content">
113
+ <h2>New Post</h2>
114
+ <%= form_with model: @post do |f| %>
115
+ ...
116
+ <% end %>
117
+ <%= link_to "Close", root_path, data: { turbo_frame: "modal" } %>
118
+ </div>
119
+ </div>
120
+ <% end %>
121
+ ```
122
+
123
+ ### Tab Navigation
124
+
125
+ ```erb
126
+ <nav>
127
+ <%= link_to "Details", post_details_path(@post), data: { turbo_frame: "tab_content" } %>
128
+ <%= link_to "Comments", post_comments_path(@post), data: { turbo_frame: "tab_content" } %>
129
+ <%= link_to "History", post_history_path(@post), data: { turbo_frame: "tab_content" } %>
130
+ </nav>
131
+
132
+ <%= turbo_frame_tag "tab_content" do %>
133
+ <%= render "details" %>
134
+ <% end %>
135
+ ```
136
+
137
+ ## Frame Events
138
+
139
+ ```javascript
140
+ // Listen for frame events
141
+ document.addEventListener("turbo:frame-load", (event) => {
142
+ console.log("Frame loaded:", event.target.id)
143
+ })
144
+
145
+ document.addEventListener("turbo:frame-missing", (event) => {
146
+ console.log("Frame not found in response:", event.target.id)
147
+ event.preventDefault() // Handle gracefully
148
+ })
149
+ ```
150
+
151
+ ## Troubleshooting
152
+
153
+ | Issue | Cause | Solution |
154
+ |-------|-------|----------|
155
+ | Frame not updating | ID mismatch | Ensure frame IDs match exactly |
156
+ | Full page reload | Missing frame in response | Add matching frame tag |
157
+ | Content disappears | Empty frame returned | Check controller response |
158
+ | Wrong frame updates | Multiple frames with same ID | Use unique IDs |