ag-cortex 0.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 (162) hide show
  1. package/.agent/commands/test-browser.md +339 -0
  2. package/.agent/rules/00-constitution.md +46 -0
  3. package/.agent/rules/project-rules.md +49 -0
  4. package/.agent/skills/agent-browser/SKILL.md +223 -0
  5. package/.agent/skills/agent-native-architecture/SKILL.md +435 -0
  6. package/.agent/skills/agent-native-architecture/references/action-parity-discipline.md +409 -0
  7. package/.agent/skills/agent-native-architecture/references/agent-execution-patterns.md +467 -0
  8. package/.agent/skills/agent-native-architecture/references/agent-native-testing.md +582 -0
  9. package/.agent/skills/agent-native-architecture/references/architecture-patterns.md +478 -0
  10. package/.agent/skills/agent-native-architecture/references/dynamic-context-injection.md +338 -0
  11. package/.agent/skills/agent-native-architecture/references/files-universal-interface.md +301 -0
  12. package/.agent/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +359 -0
  13. package/.agent/skills/agent-native-architecture/references/mcp-tool-design.md +506 -0
  14. package/.agent/skills/agent-native-architecture/references/mobile-patterns.md +871 -0
  15. package/.agent/skills/agent-native-architecture/references/product-implications.md +443 -0
  16. package/.agent/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +317 -0
  17. package/.agent/skills/agent-native-architecture/references/self-modification.md +269 -0
  18. package/.agent/skills/agent-native-architecture/references/shared-workspace-architecture.md +680 -0
  19. package/.agent/skills/agent-native-architecture/references/system-prompt-design.md +250 -0
  20. package/.agent/skills/agent-native-reviewer/SKILL.md +246 -0
  21. package/.agent/skills/andrew-kane-gem-writer/SKILL.md +184 -0
  22. package/.agent/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
  23. package/.agent/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
  24. package/.agent/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
  25. package/.agent/skills/andrew-kane-gem-writer/references/resources.md +119 -0
  26. package/.agent/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
  27. package/.agent/skills/ankane-readme-writer/SKILL.md +50 -0
  28. package/.agent/skills/architecture-strategist/SKILL.md +52 -0
  29. package/.agent/skills/best-practices-researcher/SKILL.md +100 -0
  30. package/.agent/skills/bug-reproduction-validator/SKILL.md +67 -0
  31. package/.agent/skills/code-simplicity-reviewer/SKILL.md +85 -0
  32. package/.agent/skills/coding-tutor/.claude-plugin/plugin.json +9 -0
  33. package/.agent/skills/coding-tutor/README.md +37 -0
  34. package/.agent/skills/coding-tutor/commands/quiz-me.md +1 -0
  35. package/.agent/skills/coding-tutor/commands/sync-tutorials.md +25 -0
  36. package/.agent/skills/coding-tutor/commands/teach-me.md +1 -0
  37. package/.agent/skills/coding-tutor/skills/coding-tutor/SKILL.md +214 -0
  38. package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/create_tutorial.py +202 -0
  39. package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/index_tutorials.py +203 -0
  40. package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/quiz_priority.py +190 -0
  41. package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/setup_tutorials.py +132 -0
  42. package/.agent/skills/compound-docs/SKILL.md +510 -0
  43. package/.agent/skills/compound-docs/assets/critical-pattern-template.md +34 -0
  44. package/.agent/skills/compound-docs/assets/resolution-template.md +93 -0
  45. package/.agent/skills/compound-docs/references/yaml-schema.md +65 -0
  46. package/.agent/skills/compound-docs/schema.yaml +176 -0
  47. package/.agent/skills/create-agent-skills/SKILL.md +299 -0
  48. package/.agent/skills/create-agent-skills/references/api-security.md +226 -0
  49. package/.agent/skills/create-agent-skills/references/be-clear-and-direct.md +531 -0
  50. package/.agent/skills/create-agent-skills/references/best-practices.md +404 -0
  51. package/.agent/skills/create-agent-skills/references/common-patterns.md +595 -0
  52. package/.agent/skills/create-agent-skills/references/core-principles.md +437 -0
  53. package/.agent/skills/create-agent-skills/references/executable-code.md +175 -0
  54. package/.agent/skills/create-agent-skills/references/iteration-and-testing.md +474 -0
  55. package/.agent/skills/create-agent-skills/references/official-spec.md +185 -0
  56. package/.agent/skills/create-agent-skills/references/recommended-structure.md +168 -0
  57. package/.agent/skills/create-agent-skills/references/skill-structure.md +372 -0
  58. package/.agent/skills/create-agent-skills/references/using-scripts.md +113 -0
  59. package/.agent/skills/create-agent-skills/references/using-templates.md +112 -0
  60. package/.agent/skills/create-agent-skills/references/workflows-and-validation.md +510 -0
  61. package/.agent/skills/create-agent-skills/templates/router-skill.md +73 -0
  62. package/.agent/skills/create-agent-skills/templates/simple-skill.md +33 -0
  63. package/.agent/skills/create-agent-skills/workflows/add-reference.md +96 -0
  64. package/.agent/skills/create-agent-skills/workflows/add-script.md +93 -0
  65. package/.agent/skills/create-agent-skills/workflows/add-template.md +74 -0
  66. package/.agent/skills/create-agent-skills/workflows/add-workflow.md +120 -0
  67. package/.agent/skills/create-agent-skills/workflows/audit-skill.md +138 -0
  68. package/.agent/skills/create-agent-skills/workflows/create-domain-expertise-skill.md +605 -0
  69. package/.agent/skills/create-agent-skills/workflows/create-new-skill.md +191 -0
  70. package/.agent/skills/create-agent-skills/workflows/get-guidance.md +121 -0
  71. package/.agent/skills/create-agent-skills/workflows/upgrade-to-router.md +161 -0
  72. package/.agent/skills/create-agent-skills/workflows/verify-skill.md +204 -0
  73. package/.agent/skills/data-integrity-guardian/SKILL.md +70 -0
  74. package/.agent/skills/data-migration-expert/SKILL.md +97 -0
  75. package/.agent/skills/deployment-verification-agent/SKILL.md +159 -0
  76. package/.agent/skills/design-implementation-reviewer/SKILL.md +85 -0
  77. package/.agent/skills/design-iterator/SKILL.md +197 -0
  78. package/.agent/skills/dhh-rails-reviewer/SKILL.md +45 -0
  79. package/.agent/skills/dhh-rails-style/SKILL.md +184 -0
  80. package/.agent/skills/dhh-rails-style/references/architecture.md +653 -0
  81. package/.agent/skills/dhh-rails-style/references/controllers.md +303 -0
  82. package/.agent/skills/dhh-rails-style/references/frontend.md +510 -0
  83. package/.agent/skills/dhh-rails-style/references/gems.md +266 -0
  84. package/.agent/skills/dhh-rails-style/references/models.md +359 -0
  85. package/.agent/skills/dhh-rails-style/references/testing.md +338 -0
  86. package/.agent/skills/dspy-ruby/SKILL.md +594 -0
  87. package/.agent/skills/dspy-ruby/assets/config-template.rb +359 -0
  88. package/.agent/skills/dspy-ruby/assets/module-template.rb +326 -0
  89. package/.agent/skills/dspy-ruby/assets/signature-template.rb +143 -0
  90. package/.agent/skills/dspy-ruby/references/core-concepts.md +265 -0
  91. package/.agent/skills/dspy-ruby/references/optimization.md +623 -0
  92. package/.agent/skills/dspy-ruby/references/providers.md +305 -0
  93. package/.agent/skills/every-style-editor/SKILL.md +134 -0
  94. package/.agent/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
  95. package/.agent/skills/figma-design-sync/SKILL.md +166 -0
  96. package/.agent/skills/file-todos/SKILL.md +251 -0
  97. package/.agent/skills/file-todos/assets/todo-template.md +155 -0
  98. package/.agent/skills/framework-docs-researcher/SKILL.md +83 -0
  99. package/.agent/skills/frontend-design/SKILL.md +42 -0
  100. package/.agent/skills/gemini-imagegen/SKILL.md +237 -0
  101. package/.agent/skills/gemini-imagegen/requirements.txt +2 -0
  102. package/.agent/skills/gemini-imagegen/scripts/compose_images.py +168 -0
  103. package/.agent/skills/gemini-imagegen/scripts/edit_image.py +157 -0
  104. package/.agent/skills/gemini-imagegen/scripts/gemini_images.py +265 -0
  105. package/.agent/skills/gemini-imagegen/scripts/generate_image.py +147 -0
  106. package/.agent/skills/gemini-imagegen/scripts/multi_turn_chat.py +215 -0
  107. package/.agent/skills/git-history-analyzer/SKILL.md +42 -0
  108. package/.agent/skills/git-worktree/SKILL.md +302 -0
  109. package/.agent/skills/git-worktree/scripts/worktree-manager.sh +345 -0
  110. package/.agent/skills/julik-frontend-races-reviewer/SKILL.md +222 -0
  111. package/.agent/skills/kieran-python-reviewer/SKILL.md +104 -0
  112. package/.agent/skills/kieran-rails-reviewer/SKILL.md +86 -0
  113. package/.agent/skills/kieran-typescript-reviewer/SKILL.md +95 -0
  114. package/.agent/skills/lint/SKILL.md +16 -0
  115. package/.agent/skills/pattern-recognition-specialist/SKILL.md +57 -0
  116. package/.agent/skills/performance-oracle/SKILL.md +110 -0
  117. package/.agent/skills/pr-comment-resolver/SKILL.md +69 -0
  118. package/.agent/skills/rclone/SKILL.md +150 -0
  119. package/.agent/skills/rclone/scripts/check_setup.sh +60 -0
  120. package/.agent/skills/repo-research-analyst/SKILL.md +113 -0
  121. package/.agent/skills/security-sentinel/SKILL.md +93 -0
  122. package/.agent/skills/skill-creator/SKILL.md +209 -0
  123. package/.agent/skills/skill-creator/scripts/init_skill.py +304 -0
  124. package/.agent/skills/skill-creator/scripts/package_skill.py +112 -0
  125. package/.agent/skills/skill-creator/scripts/quick_validate.py +72 -0
  126. package/.agent/skills/spec-flow-analyzer/SKILL.md +113 -0
  127. package/.agent/skills/test-agent/SKILL.md +4 -0
  128. package/.agent/workflows/agent-native-audit.md +277 -0
  129. package/.agent/workflows/ask-user-question.md +21 -0
  130. package/.agent/workflows/changelog.md +137 -0
  131. package/.agent/workflows/compound.md +202 -0
  132. package/.agent/workflows/create-agent-skill.md +8 -0
  133. package/.agent/workflows/deepen-plan-research.md +334 -0
  134. package/.agent/workflows/deepen-plan-synthesis.md +182 -0
  135. package/.agent/workflows/deepen-plan.md +79 -0
  136. package/.agent/workflows/feature-video.md +342 -0
  137. package/.agent/workflows/generate-command.md +162 -0
  138. package/.agent/workflows/heal-skill.md +142 -0
  139. package/.agent/workflows/lfg.md +20 -0
  140. package/.agent/workflows/plan-analysis.md +67 -0
  141. package/.agent/workflows/plan-next-steps.md +63 -0
  142. package/.agent/workflows/plan-review.md +33 -0
  143. package/.agent/workflows/plan-synthesis.md +106 -0
  144. package/.agent/workflows/plan.md +49 -0
  145. package/.agent/workflows/report-bug.md +150 -0
  146. package/.agent/workflows/reproduce-bug.md +99 -0
  147. package/.agent/workflows/resolve-parallel.md +34 -0
  148. package/.agent/workflows/resolve-pr-parallel.md +49 -0
  149. package/.agent/workflows/resolve-todo-parallel.md +35 -0
  150. package/.agent/workflows/review-analysis.md +145 -0
  151. package/.agent/workflows/review-synthesis.md +262 -0
  152. package/.agent/workflows/review.md +64 -0
  153. package/.agent/workflows/ship.md +90 -0
  154. package/.agent/workflows/test-command.md +3 -0
  155. package/.agent/workflows/triage.md +310 -0
  156. package/.agent/workflows/work.md +157 -0
  157. package/.agent/workflows/xcode-test.md +332 -0
  158. package/LICENSE +22 -0
  159. package/README.md +49 -0
  160. package/bin/ag-cortex.js +54 -0
  161. package/lib/core.js +165 -0
  162. package/package.json +31 -0
@@ -0,0 +1,510 @@
1
+ # Frontend - DHH Rails Style
2
+
3
+ <turbo_patterns>
4
+ ## Turbo Patterns
5
+
6
+ **Turbo Streams** for partial updates:
7
+ ```erb
8
+ <%# app/views/cards/closures/create.turbo_stream.erb %>
9
+ <%= turbo_stream.replace @card %>
10
+ ```
11
+
12
+ **Morphing** for complex updates:
13
+ ```ruby
14
+ render turbo_stream: turbo_stream.morph(@card)
15
+ ```
16
+
17
+ **Global morphing** - enable in layout:
18
+ ```ruby
19
+ turbo_refreshes_with method: :morph, scroll: :preserve
20
+ ```
21
+
22
+ **Fragment caching** with `cached: true`:
23
+ ```erb
24
+ <%= render partial: "card", collection: @cards, cached: true %>
25
+ ```
26
+
27
+ **No ViewComponents** - standard partials work fine.
28
+ </turbo_patterns>
29
+
30
+ <turbo_morphing>
31
+ ## Turbo Morphing Best Practices
32
+
33
+ **Listen for morph events** to restore client state:
34
+ ```javascript
35
+ document.addEventListener("turbo:morph-element", (event) => {
36
+ // Restore any client-side state after morph
37
+ })
38
+ ```
39
+
40
+ **Permanent elements** - skip morphing with data attribute:
41
+ ```erb
42
+ <div data-turbo-permanent id="notification-count">
43
+ <%= @count %>
44
+ </div>
45
+ ```
46
+
47
+ **Frame morphing** - add refresh attribute:
48
+ ```erb
49
+ <%= turbo_frame_tag :assignment, src: path, refresh: :morph %>
50
+ ```
51
+
52
+ **Common issues and solutions:**
53
+
54
+ | Problem | Solution |
55
+ |---------|----------|
56
+ | Timers not updating | Clear/restart in morph event listener |
57
+ | Forms resetting | Wrap form sections in turbo frames |
58
+ | Pagination breaking | Use turbo frames with `refresh: :morph` |
59
+ | Flickering on replace | Switch to morph instead of replace |
60
+ | localStorage loss | Listen to `turbo:morph-element`, restore state |
61
+ </turbo_morphing>
62
+
63
+ <turbo_frames>
64
+ ## Turbo Frames
65
+
66
+ **Lazy loading** with spinner:
67
+ ```erb
68
+ <%= turbo_frame_tag "menu",
69
+ src: menu_path,
70
+ loading: :lazy do %>
71
+ <div class="spinner">Loading...</div>
72
+ <% end %>
73
+ ```
74
+
75
+ **Inline editing** with edit/view toggle:
76
+ ```erb
77
+ <%= turbo_frame_tag dom_id(card, :edit) do %>
78
+ <%= link_to "Edit", edit_card_path(card),
79
+ data: { turbo_frame: dom_id(card, :edit) } %>
80
+ <% end %>
81
+ ```
82
+
83
+ **Target parent frame** without hardcoding:
84
+ ```erb
85
+ <%= form_with model: @card, data: { turbo_frame: "_parent" } do |f| %>
86
+ ```
87
+
88
+ **Real-time subscriptions:**
89
+ ```erb
90
+ <%= turbo_stream_from @card %>
91
+ <%= turbo_stream_from @card, :activity %>
92
+ ```
93
+ </turbo_frames>
94
+
95
+ <stimulus_controllers>
96
+ ## Stimulus Controllers
97
+
98
+ 52 controllers in Fizzy, split 62% reusable, 38% domain-specific.
99
+
100
+ **Characteristics:**
101
+ - Single responsibility per controller
102
+ - Configuration via values/classes
103
+ - Events for communication
104
+ - Private methods with #
105
+ - Most under 50 lines
106
+
107
+ **Examples:**
108
+
109
+ ```javascript
110
+ // copy-to-clipboard (25 lines)
111
+ import { Controller } from "@hotwired/stimulus"
112
+
113
+ export default class extends Controller {
114
+ static values = { content: String }
115
+
116
+ copy() {
117
+ navigator.clipboard.writeText(this.contentValue)
118
+ this.#showFeedback()
119
+ }
120
+
121
+ #showFeedback() {
122
+ this.element.classList.add("copied")
123
+ setTimeout(() => this.element.classList.remove("copied"), 1500)
124
+ }
125
+ }
126
+ ```
127
+
128
+ ```javascript
129
+ // auto-click (7 lines)
130
+ import { Controller } from "@hotwired/stimulus"
131
+
132
+ export default class extends Controller {
133
+ connect() {
134
+ this.element.click()
135
+ }
136
+ }
137
+ ```
138
+
139
+ ```javascript
140
+ // toggle-class (31 lines)
141
+ import { Controller } from "@hotwired/stimulus"
142
+
143
+ export default class extends Controller {
144
+ static classes = ["toggle"]
145
+ static values = { open: { type: Boolean, default: false } }
146
+
147
+ toggle() {
148
+ this.openValue = !this.openValue
149
+ }
150
+
151
+ openValueChanged() {
152
+ this.element.classList.toggle(this.toggleClass, this.openValue)
153
+ }
154
+ }
155
+ ```
156
+
157
+ ```javascript
158
+ // auto-submit (28 lines) - debounced form submission
159
+ import { Controller } from "@hotwired/stimulus"
160
+
161
+ export default class extends Controller {
162
+ static values = { delay: { type: Number, default: 300 } }
163
+
164
+ connect() {
165
+ this.timeout = null
166
+ }
167
+
168
+ submit() {
169
+ clearTimeout(this.timeout)
170
+ this.timeout = setTimeout(() => {
171
+ this.element.requestSubmit()
172
+ }, this.delayValue)
173
+ }
174
+
175
+ disconnect() {
176
+ clearTimeout(this.timeout)
177
+ }
178
+ }
179
+ ```
180
+
181
+ ```javascript
182
+ // dialog (45 lines) - native HTML dialog management
183
+ import { Controller } from "@hotwired/stimulus"
184
+
185
+ export default class extends Controller {
186
+ open() {
187
+ this.element.showModal()
188
+ }
189
+
190
+ close() {
191
+ this.element.close()
192
+ this.dispatch("closed")
193
+ }
194
+
195
+ clickOutside(event) {
196
+ if (event.target === this.element) this.close()
197
+ }
198
+ }
199
+ ```
200
+
201
+ ```javascript
202
+ // local-time (40 lines) - relative time display
203
+ import { Controller } from "@hotwired/stimulus"
204
+
205
+ export default class extends Controller {
206
+ static values = { datetime: String }
207
+
208
+ connect() {
209
+ this.#updateTime()
210
+ }
211
+
212
+ #updateTime() {
213
+ const date = new Date(this.datetimeValue)
214
+ const now = new Date()
215
+ const diffMinutes = Math.floor((now - date) / 60000)
216
+
217
+ if (diffMinutes < 60) {
218
+ this.element.textContent = `${diffMinutes}m ago`
219
+ } else if (diffMinutes < 1440) {
220
+ this.element.textContent = `${Math.floor(diffMinutes / 60)}h ago`
221
+ } else {
222
+ this.element.textContent = `${Math.floor(diffMinutes / 1440)}d ago`
223
+ }
224
+ }
225
+ }
226
+ ```
227
+ </stimulus_controllers>
228
+
229
+ <stimulus_best_practices>
230
+ ## Stimulus Best Practices
231
+
232
+ **Values API** over getAttribute:
233
+ ```javascript
234
+ // Good
235
+ static values = { delay: { type: Number, default: 300 } }
236
+
237
+ // Avoid
238
+ this.element.getAttribute("data-delay")
239
+ ```
240
+
241
+ **Cleanup in disconnect:**
242
+ ```javascript
243
+ disconnect() {
244
+ clearTimeout(this.timeout)
245
+ this.observer?.disconnect()
246
+ document.removeEventListener("keydown", this.boundHandler)
247
+ }
248
+ ```
249
+
250
+ **Action filters** - `:self` prevents bubbling:
251
+ ```erb
252
+ <div data-action="click->menu#toggle:self">
253
+ ```
254
+
255
+ **Helper extraction** - shared utilities in separate modules:
256
+ ```javascript
257
+ // app/javascript/helpers/timing.js
258
+ export function debounce(fn, delay) {
259
+ let timeout
260
+ return (...args) => {
261
+ clearTimeout(timeout)
262
+ timeout = setTimeout(() => fn(...args), delay)
263
+ }
264
+ }
265
+ ```
266
+
267
+ **Event dispatching** for loose coupling:
268
+ ```javascript
269
+ this.dispatch("selected", { detail: { id: this.idValue } })
270
+ ```
271
+ </stimulus_best_practices>
272
+
273
+ <view_helpers>
274
+ ## View Helpers (Stimulus-Integrated)
275
+
276
+ **Dialog helper:**
277
+ ```ruby
278
+ def dialog_tag(id, &block)
279
+ tag.dialog(
280
+ id: id,
281
+ data: {
282
+ controller: "dialog",
283
+ action: "click->dialog#clickOutside keydown.esc->dialog#close"
284
+ },
285
+ &block
286
+ )
287
+ end
288
+ ```
289
+
290
+ **Auto-submit form helper:**
291
+ ```ruby
292
+ def auto_submit_form_with(model:, delay: 300, **options, &block)
293
+ form_with(
294
+ model: model,
295
+ data: {
296
+ controller: "auto-submit",
297
+ auto_submit_delay_value: delay,
298
+ action: "input->auto-submit#submit"
299
+ },
300
+ **options,
301
+ &block
302
+ )
303
+ end
304
+ ```
305
+
306
+ **Copy button helper:**
307
+ ```ruby
308
+ def copy_button(content:, label: "Copy")
309
+ tag.button(
310
+ label,
311
+ data: {
312
+ controller: "copy",
313
+ copy_content_value: content,
314
+ action: "click->copy#copy"
315
+ }
316
+ )
317
+ end
318
+ ```
319
+ </view_helpers>
320
+
321
+ <css_architecture>
322
+ ## CSS Architecture
323
+
324
+ Vanilla CSS with modern features, no preprocessors.
325
+
326
+ **CSS @layer** for cascade control:
327
+ ```css
328
+ @layer reset, base, components, modules, utilities;
329
+
330
+ @layer reset {
331
+ *, *::before, *::after { box-sizing: border-box; }
332
+ }
333
+
334
+ @layer base {
335
+ body { font-family: var(--font-sans); }
336
+ }
337
+
338
+ @layer components {
339
+ .btn { /* button styles */ }
340
+ }
341
+
342
+ @layer modules {
343
+ .card { /* card module styles */ }
344
+ }
345
+
346
+ @layer utilities {
347
+ .hidden { display: none; }
348
+ }
349
+ ```
350
+
351
+ **OKLCH color system** for perceptual uniformity:
352
+ ```css
353
+ :root {
354
+ --color-primary: oklch(60% 0.15 250);
355
+ --color-success: oklch(65% 0.2 145);
356
+ --color-warning: oklch(75% 0.15 85);
357
+ --color-danger: oklch(55% 0.2 25);
358
+ }
359
+ ```
360
+
361
+ **Dark mode** via CSS variables:
362
+ ```css
363
+ :root {
364
+ --bg: oklch(98% 0 0);
365
+ --text: oklch(20% 0 0);
366
+ }
367
+
368
+ @media (prefers-color-scheme: dark) {
369
+ :root {
370
+ --bg: oklch(15% 0 0);
371
+ --text: oklch(90% 0 0);
372
+ }
373
+ }
374
+ ```
375
+
376
+ **Native CSS nesting:**
377
+ ```css
378
+ .card {
379
+ padding: var(--space-4);
380
+
381
+ & .title {
382
+ font-weight: bold;
383
+ }
384
+
385
+ &:hover {
386
+ background: var(--bg-hover);
387
+ }
388
+ }
389
+ ```
390
+
391
+ **~60 minimal utilities** vs Tailwind's hundreds.
392
+
393
+ **Modern features used:**
394
+ - `@starting-style` for enter animations
395
+ - `color-mix()` for color manipulation
396
+ - `:has()` for parent selection
397
+ - Logical properties (`margin-inline`, `padding-block`)
398
+ - Container queries
399
+ </css_architecture>
400
+
401
+ <view_patterns>
402
+ ## View Patterns
403
+
404
+ **Standard partials** - no ViewComponents:
405
+ ```erb
406
+ <%# app/views/cards/_card.html.erb %>
407
+ <article id="<%= dom_id(card) %>" class="card">
408
+ <%= render "cards/header", card: card %>
409
+ <%= render "cards/body", card: card %>
410
+ <%= render "cards/footer", card: card %>
411
+ </article>
412
+ ```
413
+
414
+ **Fragment caching:**
415
+ ```erb
416
+ <% cache card do %>
417
+ <%= render "cards/card", card: card %>
418
+ <% end %>
419
+ ```
420
+
421
+ **Collection caching:**
422
+ ```erb
423
+ <%= render partial: "card", collection: @cards, cached: true %>
424
+ ```
425
+
426
+ **Simple component naming** - no strict BEM:
427
+ ```css
428
+ .card { }
429
+ .card .title { }
430
+ .card .actions { }
431
+ .card.golden { }
432
+ .card.closed { }
433
+ ```
434
+ </view_patterns>
435
+
436
+ <caching_with_personalization>
437
+ ## User-Specific Content in Caches
438
+
439
+ Move personalization to client-side JavaScript to preserve caching:
440
+
441
+ ```erb
442
+ <%# Cacheable fragment %>
443
+ <% cache card do %>
444
+ <article class="card"
445
+ data-creator-id="<%= card.creator_id %>"
446
+ data-controller="ownership"
447
+ data-ownership-current-user-value="<%= Current.user.id %>">
448
+ <button data-ownership-target="ownerOnly" class="hidden">Delete</button>
449
+ </article>
450
+ <% end %>
451
+ ```
452
+
453
+ ```javascript
454
+ // Reveal user-specific elements after cache hit
455
+ export default class extends Controller {
456
+ static values = { currentUser: Number }
457
+ static targets = ["ownerOnly"]
458
+
459
+ connect() {
460
+ const creatorId = parseInt(this.element.dataset.creatorId)
461
+ if (creatorId === this.currentUserValue) {
462
+ this.ownerOnlyTargets.forEach(el => el.classList.remove("hidden"))
463
+ }
464
+ }
465
+ }
466
+ ```
467
+
468
+ **Extract dynamic content** to separate frames:
469
+ ```erb
470
+ <% cache [card, board] do %>
471
+ <article class="card">
472
+ <%= turbo_frame_tag card, :assignment,
473
+ src: card_assignment_path(card),
474
+ refresh: :morph %>
475
+ </article>
476
+ <% end %>
477
+ ```
478
+
479
+ Assignment dropdown updates independently without invalidating parent cache.
480
+ </caching_with_personalization>
481
+
482
+ <broadcasting>
483
+ ## Broadcasting with Turbo Streams
484
+
485
+ **Model callbacks** for real-time updates:
486
+ ```ruby
487
+ class Card < ApplicationRecord
488
+ include Broadcastable
489
+
490
+ after_create_commit :broadcast_created
491
+ after_update_commit :broadcast_updated
492
+ after_destroy_commit :broadcast_removed
493
+
494
+ private
495
+ def broadcast_created
496
+ broadcast_append_to [Current.account, board], :cards
497
+ end
498
+
499
+ def broadcast_updated
500
+ broadcast_replace_to [Current.account, board], :cards
501
+ end
502
+
503
+ def broadcast_removed
504
+ broadcast_remove_to [Current.account, board], :cards
505
+ end
506
+ end
507
+ ```
508
+
509
+ **Scope by tenant** using `[Current.account, resource]` pattern.
510
+ </broadcasting>