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.
- package/.agent/commands/test-browser.md +339 -0
- package/.agent/rules/00-constitution.md +46 -0
- package/.agent/rules/project-rules.md +49 -0
- package/.agent/skills/agent-browser/SKILL.md +223 -0
- package/.agent/skills/agent-native-architecture/SKILL.md +435 -0
- package/.agent/skills/agent-native-architecture/references/action-parity-discipline.md +409 -0
- package/.agent/skills/agent-native-architecture/references/agent-execution-patterns.md +467 -0
- package/.agent/skills/agent-native-architecture/references/agent-native-testing.md +582 -0
- package/.agent/skills/agent-native-architecture/references/architecture-patterns.md +478 -0
- package/.agent/skills/agent-native-architecture/references/dynamic-context-injection.md +338 -0
- package/.agent/skills/agent-native-architecture/references/files-universal-interface.md +301 -0
- package/.agent/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +359 -0
- package/.agent/skills/agent-native-architecture/references/mcp-tool-design.md +506 -0
- package/.agent/skills/agent-native-architecture/references/mobile-patterns.md +871 -0
- package/.agent/skills/agent-native-architecture/references/product-implications.md +443 -0
- package/.agent/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +317 -0
- package/.agent/skills/agent-native-architecture/references/self-modification.md +269 -0
- package/.agent/skills/agent-native-architecture/references/shared-workspace-architecture.md +680 -0
- package/.agent/skills/agent-native-architecture/references/system-prompt-design.md +250 -0
- package/.agent/skills/agent-native-reviewer/SKILL.md +246 -0
- package/.agent/skills/andrew-kane-gem-writer/SKILL.md +184 -0
- package/.agent/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
- package/.agent/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
- package/.agent/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
- package/.agent/skills/andrew-kane-gem-writer/references/resources.md +119 -0
- package/.agent/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
- package/.agent/skills/ankane-readme-writer/SKILL.md +50 -0
- package/.agent/skills/architecture-strategist/SKILL.md +52 -0
- package/.agent/skills/best-practices-researcher/SKILL.md +100 -0
- package/.agent/skills/bug-reproduction-validator/SKILL.md +67 -0
- package/.agent/skills/code-simplicity-reviewer/SKILL.md +85 -0
- package/.agent/skills/coding-tutor/.claude-plugin/plugin.json +9 -0
- package/.agent/skills/coding-tutor/README.md +37 -0
- package/.agent/skills/coding-tutor/commands/quiz-me.md +1 -0
- package/.agent/skills/coding-tutor/commands/sync-tutorials.md +25 -0
- package/.agent/skills/coding-tutor/commands/teach-me.md +1 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/SKILL.md +214 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/create_tutorial.py +202 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/index_tutorials.py +203 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/quiz_priority.py +190 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/setup_tutorials.py +132 -0
- package/.agent/skills/compound-docs/SKILL.md +510 -0
- package/.agent/skills/compound-docs/assets/critical-pattern-template.md +34 -0
- package/.agent/skills/compound-docs/assets/resolution-template.md +93 -0
- package/.agent/skills/compound-docs/references/yaml-schema.md +65 -0
- package/.agent/skills/compound-docs/schema.yaml +176 -0
- package/.agent/skills/create-agent-skills/SKILL.md +299 -0
- package/.agent/skills/create-agent-skills/references/api-security.md +226 -0
- package/.agent/skills/create-agent-skills/references/be-clear-and-direct.md +531 -0
- package/.agent/skills/create-agent-skills/references/best-practices.md +404 -0
- package/.agent/skills/create-agent-skills/references/common-patterns.md +595 -0
- package/.agent/skills/create-agent-skills/references/core-principles.md +437 -0
- package/.agent/skills/create-agent-skills/references/executable-code.md +175 -0
- package/.agent/skills/create-agent-skills/references/iteration-and-testing.md +474 -0
- package/.agent/skills/create-agent-skills/references/official-spec.md +185 -0
- package/.agent/skills/create-agent-skills/references/recommended-structure.md +168 -0
- package/.agent/skills/create-agent-skills/references/skill-structure.md +372 -0
- package/.agent/skills/create-agent-skills/references/using-scripts.md +113 -0
- package/.agent/skills/create-agent-skills/references/using-templates.md +112 -0
- package/.agent/skills/create-agent-skills/references/workflows-and-validation.md +510 -0
- package/.agent/skills/create-agent-skills/templates/router-skill.md +73 -0
- package/.agent/skills/create-agent-skills/templates/simple-skill.md +33 -0
- package/.agent/skills/create-agent-skills/workflows/add-reference.md +96 -0
- package/.agent/skills/create-agent-skills/workflows/add-script.md +93 -0
- package/.agent/skills/create-agent-skills/workflows/add-template.md +74 -0
- package/.agent/skills/create-agent-skills/workflows/add-workflow.md +120 -0
- package/.agent/skills/create-agent-skills/workflows/audit-skill.md +138 -0
- package/.agent/skills/create-agent-skills/workflows/create-domain-expertise-skill.md +605 -0
- package/.agent/skills/create-agent-skills/workflows/create-new-skill.md +191 -0
- package/.agent/skills/create-agent-skills/workflows/get-guidance.md +121 -0
- package/.agent/skills/create-agent-skills/workflows/upgrade-to-router.md +161 -0
- package/.agent/skills/create-agent-skills/workflows/verify-skill.md +204 -0
- package/.agent/skills/data-integrity-guardian/SKILL.md +70 -0
- package/.agent/skills/data-migration-expert/SKILL.md +97 -0
- package/.agent/skills/deployment-verification-agent/SKILL.md +159 -0
- package/.agent/skills/design-implementation-reviewer/SKILL.md +85 -0
- package/.agent/skills/design-iterator/SKILL.md +197 -0
- package/.agent/skills/dhh-rails-reviewer/SKILL.md +45 -0
- package/.agent/skills/dhh-rails-style/SKILL.md +184 -0
- package/.agent/skills/dhh-rails-style/references/architecture.md +653 -0
- package/.agent/skills/dhh-rails-style/references/controllers.md +303 -0
- package/.agent/skills/dhh-rails-style/references/frontend.md +510 -0
- package/.agent/skills/dhh-rails-style/references/gems.md +266 -0
- package/.agent/skills/dhh-rails-style/references/models.md +359 -0
- package/.agent/skills/dhh-rails-style/references/testing.md +338 -0
- package/.agent/skills/dspy-ruby/SKILL.md +594 -0
- package/.agent/skills/dspy-ruby/assets/config-template.rb +359 -0
- package/.agent/skills/dspy-ruby/assets/module-template.rb +326 -0
- package/.agent/skills/dspy-ruby/assets/signature-template.rb +143 -0
- package/.agent/skills/dspy-ruby/references/core-concepts.md +265 -0
- package/.agent/skills/dspy-ruby/references/optimization.md +623 -0
- package/.agent/skills/dspy-ruby/references/providers.md +305 -0
- package/.agent/skills/every-style-editor/SKILL.md +134 -0
- package/.agent/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
- package/.agent/skills/figma-design-sync/SKILL.md +166 -0
- package/.agent/skills/file-todos/SKILL.md +251 -0
- package/.agent/skills/file-todos/assets/todo-template.md +155 -0
- package/.agent/skills/framework-docs-researcher/SKILL.md +83 -0
- package/.agent/skills/frontend-design/SKILL.md +42 -0
- package/.agent/skills/gemini-imagegen/SKILL.md +237 -0
- package/.agent/skills/gemini-imagegen/requirements.txt +2 -0
- package/.agent/skills/gemini-imagegen/scripts/compose_images.py +168 -0
- package/.agent/skills/gemini-imagegen/scripts/edit_image.py +157 -0
- package/.agent/skills/gemini-imagegen/scripts/gemini_images.py +265 -0
- package/.agent/skills/gemini-imagegen/scripts/generate_image.py +147 -0
- package/.agent/skills/gemini-imagegen/scripts/multi_turn_chat.py +215 -0
- package/.agent/skills/git-history-analyzer/SKILL.md +42 -0
- package/.agent/skills/git-worktree/SKILL.md +302 -0
- package/.agent/skills/git-worktree/scripts/worktree-manager.sh +345 -0
- package/.agent/skills/julik-frontend-races-reviewer/SKILL.md +222 -0
- package/.agent/skills/kieran-python-reviewer/SKILL.md +104 -0
- package/.agent/skills/kieran-rails-reviewer/SKILL.md +86 -0
- package/.agent/skills/kieran-typescript-reviewer/SKILL.md +95 -0
- package/.agent/skills/lint/SKILL.md +16 -0
- package/.agent/skills/pattern-recognition-specialist/SKILL.md +57 -0
- package/.agent/skills/performance-oracle/SKILL.md +110 -0
- package/.agent/skills/pr-comment-resolver/SKILL.md +69 -0
- package/.agent/skills/rclone/SKILL.md +150 -0
- package/.agent/skills/rclone/scripts/check_setup.sh +60 -0
- package/.agent/skills/repo-research-analyst/SKILL.md +113 -0
- package/.agent/skills/security-sentinel/SKILL.md +93 -0
- package/.agent/skills/skill-creator/SKILL.md +209 -0
- package/.agent/skills/skill-creator/scripts/init_skill.py +304 -0
- package/.agent/skills/skill-creator/scripts/package_skill.py +112 -0
- package/.agent/skills/skill-creator/scripts/quick_validate.py +72 -0
- package/.agent/skills/spec-flow-analyzer/SKILL.md +113 -0
- package/.agent/skills/test-agent/SKILL.md +4 -0
- package/.agent/workflows/agent-native-audit.md +277 -0
- package/.agent/workflows/ask-user-question.md +21 -0
- package/.agent/workflows/changelog.md +137 -0
- package/.agent/workflows/compound.md +202 -0
- package/.agent/workflows/create-agent-skill.md +8 -0
- package/.agent/workflows/deepen-plan-research.md +334 -0
- package/.agent/workflows/deepen-plan-synthesis.md +182 -0
- package/.agent/workflows/deepen-plan.md +79 -0
- package/.agent/workflows/feature-video.md +342 -0
- package/.agent/workflows/generate-command.md +162 -0
- package/.agent/workflows/heal-skill.md +142 -0
- package/.agent/workflows/lfg.md +20 -0
- package/.agent/workflows/plan-analysis.md +67 -0
- package/.agent/workflows/plan-next-steps.md +63 -0
- package/.agent/workflows/plan-review.md +33 -0
- package/.agent/workflows/plan-synthesis.md +106 -0
- package/.agent/workflows/plan.md +49 -0
- package/.agent/workflows/report-bug.md +150 -0
- package/.agent/workflows/reproduce-bug.md +99 -0
- package/.agent/workflows/resolve-parallel.md +34 -0
- package/.agent/workflows/resolve-pr-parallel.md +49 -0
- package/.agent/workflows/resolve-todo-parallel.md +35 -0
- package/.agent/workflows/review-analysis.md +145 -0
- package/.agent/workflows/review-synthesis.md +262 -0
- package/.agent/workflows/review.md +64 -0
- package/.agent/workflows/ship.md +90 -0
- package/.agent/workflows/test-command.md +3 -0
- package/.agent/workflows/triage.md +310 -0
- package/.agent/workflows/work.md +157 -0
- package/.agent/workflows/xcode-test.md +332 -0
- package/LICENSE +22 -0
- package/README.md +49 -0
- package/bin/ag-cortex.js +54 -0
- package/lib/core.js +165 -0
- 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>
|