claude-agent-framework 1.0.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 (111) hide show
  1. package/README.md +128 -0
  2. package/bin/claude-framework +3 -0
  3. package/framework/agents/design-lead.md +240 -0
  4. package/framework/agents/product-owner.md +179 -0
  5. package/framework/agents/tech-lead.md +226 -0
  6. package/framework/commands/ayuda.md +127 -0
  7. package/framework/commands/a/303/261adir.md +98 -0
  8. package/framework/commands/backup.md +397 -0
  9. package/framework/commands/cambiar.md +110 -0
  10. package/framework/commands/cloud.md +457 -0
  11. package/framework/commands/code.md +142 -0
  12. package/framework/commands/debug.md +334 -0
  13. package/framework/commands/deploy.md +383 -0
  14. package/framework/commands/deshacer.md +120 -0
  15. package/framework/commands/estado.md +218 -0
  16. package/framework/commands/explica.md +227 -0
  17. package/framework/commands/feature.md +120 -0
  18. package/framework/commands/git.md +427 -0
  19. package/framework/commands/historial.md +202 -0
  20. package/framework/commands/learn.md +408 -0
  21. package/framework/commands/movil.md +245 -0
  22. package/framework/commands/nuevo.md +118 -0
  23. package/framework/commands/plan.md +134 -0
  24. package/framework/commands/prd.md +113 -0
  25. package/framework/commands/probar.md +148 -0
  26. package/framework/commands/revisar.md +208 -0
  27. package/framework/commands/seeds.md +230 -0
  28. package/framework/commands/seguridad.md +226 -0
  29. package/framework/commands/tasks.md +157 -0
  30. package/framework/skills/architecture/algorithms.md +970 -0
  31. package/framework/skills/architecture/clean-code.md +1080 -0
  32. package/framework/skills/architecture/design-patterns.md +1984 -0
  33. package/framework/skills/architecture/functional-programming.md +972 -0
  34. package/framework/skills/architecture/solid.md +991 -0
  35. package/framework/skills/cloud/cloud-aws.md +848 -0
  36. package/framework/skills/cloud/cloud-azure.md +931 -0
  37. package/framework/skills/cloud/cloud-gcp.md +848 -0
  38. package/framework/skills/cloud/message-queues.md +1229 -0
  39. package/framework/skills/core/accessibility.md +401 -0
  40. package/framework/skills/core/api.md +474 -0
  41. package/framework/skills/core/authentication.md +306 -0
  42. package/framework/skills/core/authorization.md +388 -0
  43. package/framework/skills/core/background-jobs.md +341 -0
  44. package/framework/skills/core/caching.md +473 -0
  45. package/framework/skills/core/code-review.md +341 -0
  46. package/framework/skills/core/controllers.md +290 -0
  47. package/framework/skills/core/cua.md +285 -0
  48. package/framework/skills/core/documentation.md +472 -0
  49. package/framework/skills/core/file-uploads.md +351 -0
  50. package/framework/skills/core/hotwire-native.md +296 -0
  51. package/framework/skills/core/hotwire.md +278 -0
  52. package/framework/skills/core/i18n.md +334 -0
  53. package/framework/skills/core/imports-exports.md +750 -0
  54. package/framework/skills/core/infrastructure.md +337 -0
  55. package/framework/skills/core/models.md +228 -0
  56. package/framework/skills/core/notifications.md +672 -0
  57. package/framework/skills/core/payments.md +581 -0
  58. package/framework/skills/core/performance.md +361 -0
  59. package/framework/skills/core/rails-scaffold.md +131 -0
  60. package/framework/skills/core/search.md +518 -0
  61. package/framework/skills/core/security.md +565 -0
  62. package/framework/skills/core/seeds.md +307 -0
  63. package/framework/skills/core/seo.md +542 -0
  64. package/framework/skills/core/testing.md +393 -0
  65. package/framework/skills/core/views.md +260 -0
  66. package/framework/skills/core/websockets.md +564 -0
  67. package/framework/skills/data/advanced-sql.md +1204 -0
  68. package/framework/skills/data/nosql.md +1141 -0
  69. package/framework/skills/devops/containers-advanced.md +1237 -0
  70. package/framework/skills/devops/debugging.md +834 -0
  71. package/framework/skills/devops/git-workflow.md +752 -0
  72. package/framework/skills/devops/networking.md +932 -0
  73. package/framework/skills/devops/shell-scripting.md +1132 -0
  74. package/framework/sub-agents/architecture-patterns-agent.md +1450 -0
  75. package/framework/sub-agents/cloud-agent.md +677 -0
  76. package/framework/sub-agents/data.md +504 -0
  77. package/framework/sub-agents/debugging-agent.md +554 -0
  78. package/framework/sub-agents/devops.md +483 -0
  79. package/framework/sub-agents/docs.md +176 -0
  80. package/framework/sub-agents/frontend-dev.md +349 -0
  81. package/framework/sub-agents/git-workflow-agent.md +697 -0
  82. package/framework/sub-agents/integrations.md +630 -0
  83. package/framework/sub-agents/native-dev.md +434 -0
  84. package/framework/sub-agents/qa.md +138 -0
  85. package/framework/sub-agents/rails-dev.md +375 -0
  86. package/framework/sub-agents/security.md +526 -0
  87. package/framework/sub-agents/ui.md +437 -0
  88. package/framework/sub-agents/ux.md +284 -0
  89. package/framework/templates/api-spec.md +500 -0
  90. package/framework/templates/component-spec.md +248 -0
  91. package/framework/templates/feature.json +13 -0
  92. package/framework/templates/model-spec.md +318 -0
  93. package/framework/templates/prd-template.md +80 -0
  94. package/framework/templates/task-plan.md +122 -0
  95. package/framework/templates/task-user-story.md +52 -0
  96. package/framework/templates/technical-spec.md +260 -0
  97. package/framework/templates/user-story.md +95 -0
  98. package/package.json +42 -0
  99. package/project-templates/CLAUDE.md +42 -0
  100. package/project-templates/contexts/architecture.md +25 -0
  101. package/project-templates/contexts/conventions.md +46 -0
  102. package/project-templates/contexts/design-system.md +47 -0
  103. package/project-templates/contexts/requirements.md +38 -0
  104. package/project-templates/contexts/stack.md +30 -0
  105. package/project-templates/history/active/models.md +11 -0
  106. package/project-templates/history/changelog.md +15 -0
  107. package/project-templates/workspace/.gitkeep +0 -0
  108. package/src/cli.js +52 -0
  109. package/src/init.js +104 -0
  110. package/src/status.js +75 -0
  111. package/src/update.js +88 -0
@@ -0,0 +1,393 @@
1
+ # Skill: Testing
2
+
3
+ ## Purpose
4
+ Write comprehensive tests using RSpec, FactoryBot, Faker, and Shoulda Matchers.
5
+
6
+ ## Setup
7
+
8
+ ### Gemfile
9
+ ```ruby
10
+ group :development, :test do
11
+ gem "rspec-rails"
12
+ gem "factory_bot_rails"
13
+ gem "faker"
14
+ end
15
+
16
+ group :test do
17
+ gem "shoulda-matchers"
18
+ gem "capybara"
19
+ gem "selenium-webdriver"
20
+ end
21
+ ```
22
+
23
+ ### Install RSpec
24
+ ```bash
25
+ rails generate rspec:install
26
+ ```
27
+
28
+ ### Configure RSpec
29
+ ```ruby
30
+ # spec/rails_helper.rb
31
+ require "spec_helper"
32
+ ENV["RAILS_ENV"] ||= "test"
33
+ require_relative "../config/environment"
34
+
35
+ abort("Running in production!") if Rails.env.production?
36
+ require "rspec/rails"
37
+
38
+ Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |f| require f }
39
+
40
+ RSpec.configure do |config|
41
+ config.fixture_paths = [Rails.root.join("spec/fixtures")]
42
+ config.use_transactional_fixtures = true
43
+ config.infer_spec_type_from_file_location!
44
+ config.filter_rails_from_backtrace!
45
+
46
+ config.include FactoryBot::Syntax::Methods
47
+ end
48
+
49
+ Shoulda::Matchers.configure do |config|
50
+ config.integrate do |with|
51
+ with.test_framework :rspec
52
+ with.library :rails
53
+ end
54
+ end
55
+ ```
56
+
57
+ ## Factories
58
+
59
+ ### Basic Factory
60
+ ```ruby
61
+ # spec/factories/users.rb
62
+ FactoryBot.define do
63
+ factory :user do
64
+ email_address { Faker::Internet.unique.email }
65
+ password { "password123" }
66
+ password_confirmation { "password123" }
67
+
68
+ trait :admin do
69
+ role { "admin" }
70
+ end
71
+
72
+ trait :with_articles do
73
+ transient do
74
+ articles_count { 3 }
75
+ end
76
+
77
+ after(:create) do |user, evaluator|
78
+ create_list(:article, evaluator.articles_count, user: user)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ ```
84
+
85
+ ### Associated Factory
86
+ ```ruby
87
+ # spec/factories/articles.rb
88
+ FactoryBot.define do
89
+ factory :article do
90
+ title { Faker::Lorem.sentence }
91
+ body { Faker::Lorem.paragraphs(number: 3).join("\n\n") }
92
+ published { false }
93
+ user
94
+
95
+ trait :published do
96
+ published { true }
97
+ end
98
+
99
+ trait :with_comments do
100
+ transient do
101
+ comments_count { 5 }
102
+ end
103
+
104
+ after(:create) do |article, evaluator|
105
+ create_list(:comment, evaluator.comments_count, article: article)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ ```
111
+
112
+ ## Model Specs
113
+
114
+ ```ruby
115
+ # spec/models/article_spec.rb
116
+ require "rails_helper"
117
+
118
+ RSpec.describe Article, type: :model do
119
+ describe "validations" do
120
+ it { should validate_presence_of(:title) }
121
+ it { should validate_length_of(:title).is_at_most(255) }
122
+ it { should validate_presence_of(:body) }
123
+ end
124
+
125
+ describe "associations" do
126
+ it { should belong_to(:user) }
127
+ it { should have_many(:comments).dependent(:destroy) }
128
+ it { should have_many(:taggings).dependent(:destroy) }
129
+ it { should have_many(:tags).through(:taggings) }
130
+ end
131
+
132
+ describe "scopes" do
133
+ describe ".published" do
134
+ it "returns only published articles" do
135
+ published = create(:article, :published)
136
+ draft = create(:article, published: false)
137
+
138
+ expect(Article.published).to include(published)
139
+ expect(Article.published).not_to include(draft)
140
+ end
141
+ end
142
+
143
+ describe ".recent" do
144
+ it "orders by created_at descending" do
145
+ old = create(:article, created_at: 1.week.ago)
146
+ new = create(:article, created_at: 1.day.ago)
147
+
148
+ expect(Article.recent).to eq([new, old])
149
+ end
150
+ end
151
+ end
152
+
153
+ describe "#publish!" do
154
+ it "marks article as published" do
155
+ article = create(:article, published: false)
156
+
157
+ article.publish!
158
+
159
+ expect(article.reload).to be_published
160
+ end
161
+ end
162
+
163
+ describe "callbacks" do
164
+ it "generates slug before save" do
165
+ article = build(:article, title: "Hello World")
166
+
167
+ article.save
168
+
169
+ expect(article.slug).to eq("hello-world")
170
+ end
171
+ end
172
+ end
173
+ ```
174
+
175
+ ## Request Specs
176
+
177
+ ```ruby
178
+ # spec/requests/articles_spec.rb
179
+ require "rails_helper"
180
+
181
+ RSpec.describe "Articles", type: :request do
182
+ let(:user) { create(:user) }
183
+ let(:article) { create(:article, user: user) }
184
+
185
+ describe "GET /articles" do
186
+ it "returns success" do
187
+ get articles_path
188
+ expect(response).to have_http_status(:success)
189
+ end
190
+
191
+ it "displays articles" do
192
+ article = create(:article, :published)
193
+ get articles_path
194
+ expect(response.body).to include(article.title)
195
+ end
196
+ end
197
+
198
+ describe "GET /articles/:id" do
199
+ context "when article is published" do
200
+ it "returns success" do
201
+ article = create(:article, :published)
202
+ get article_path(article)
203
+ expect(response).to have_http_status(:success)
204
+ end
205
+ end
206
+
207
+ context "when article is draft" do
208
+ it "redirects unauthorized users" do
209
+ article = create(:article, published: false)
210
+ get article_path(article)
211
+ expect(response).to redirect_to(new_session_path)
212
+ end
213
+ end
214
+ end
215
+
216
+ describe "POST /articles" do
217
+ context "when authenticated" do
218
+ before { sign_in(user) }
219
+
220
+ it "creates article with valid params" do
221
+ expect {
222
+ post articles_path, params: { article: { title: "Test", body: "Content" } }
223
+ }.to change(Article, :count).by(1)
224
+ end
225
+
226
+ it "does not create with invalid params" do
227
+ expect {
228
+ post articles_path, params: { article: { title: "", body: "" } }
229
+ }.not_to change(Article, :count)
230
+ end
231
+ end
232
+
233
+ context "when not authenticated" do
234
+ it "redirects to login" do
235
+ post articles_path, params: { article: { title: "Test", body: "Content" } }
236
+ expect(response).to redirect_to(new_session_path)
237
+ end
238
+ end
239
+ end
240
+
241
+ describe "PATCH /articles/:id" do
242
+ before { sign_in(user) }
243
+
244
+ it "updates article" do
245
+ patch article_path(article), params: { article: { title: "Updated" } }
246
+ expect(article.reload.title).to eq("Updated")
247
+ end
248
+ end
249
+
250
+ describe "DELETE /articles/:id" do
251
+ before { sign_in(user) }
252
+
253
+ it "destroys article" do
254
+ article # create it
255
+ expect {
256
+ delete article_path(article)
257
+ }.to change(Article, :count).by(-1)
258
+ end
259
+ end
260
+ end
261
+ ```
262
+
263
+ ## System Specs
264
+
265
+ ```ruby
266
+ # spec/system/articles_spec.rb
267
+ require "rails_helper"
268
+
269
+ RSpec.describe "Articles", type: :system do
270
+ let(:user) { create(:user) }
271
+
272
+ before do
273
+ driven_by(:selenium_chrome_headless)
274
+ end
275
+
276
+ describe "creating an article" do
277
+ before { sign_in(user) }
278
+
279
+ it "allows user to create article" do
280
+ visit new_article_path
281
+
282
+ fill_in "Title", with: "My Article"
283
+ fill_in "Body", with: "Article content here"
284
+ click_button "Create Article"
285
+
286
+ expect(page).to have_content("Article created successfully")
287
+ expect(page).to have_content("My Article")
288
+ end
289
+
290
+ it "shows validation errors" do
291
+ visit new_article_path
292
+
293
+ click_button "Create Article"
294
+
295
+ expect(page).to have_content("can't be blank")
296
+ end
297
+ end
298
+
299
+ describe "editing an article" do
300
+ let!(:article) { create(:article, user: user, title: "Original") }
301
+
302
+ before { sign_in(user) }
303
+
304
+ it "allows owner to edit" do
305
+ visit article_path(article)
306
+ click_link "Edit"
307
+
308
+ fill_in "Title", with: "Updated Title"
309
+ click_button "Update Article"
310
+
311
+ expect(page).to have_content("Updated Title")
312
+ end
313
+ end
314
+
315
+ describe "with Turbo interactions" do
316
+ before { sign_in(user) }
317
+
318
+ it "adds comment without page reload", js: true do
319
+ article = create(:article, :published)
320
+ visit article_path(article)
321
+
322
+ fill_in "Comment", with: "Great article!"
323
+ click_button "Post Comment"
324
+
325
+ within("#comments") do
326
+ expect(page).to have_content("Great article!")
327
+ end
328
+ end
329
+ end
330
+ end
331
+ ```
332
+
333
+ ## Test Helpers
334
+
335
+ ```ruby
336
+ # spec/support/authentication_helper.rb
337
+ module AuthenticationHelper
338
+ def sign_in(user)
339
+ post session_path, params: {
340
+ email_address: user.email_address,
341
+ password: "password123"
342
+ }
343
+ end
344
+ end
345
+
346
+ RSpec.configure do |config|
347
+ config.include AuthenticationHelper, type: :request
348
+ end
349
+
350
+ # spec/support/system_helper.rb
351
+ module SystemHelper
352
+ def sign_in(user)
353
+ visit new_session_path
354
+ fill_in "Email", with: user.email_address
355
+ fill_in "Password", with: "password123"
356
+ click_button "Sign In"
357
+ end
358
+ end
359
+
360
+ RSpec.configure do |config|
361
+ config.include SystemHelper, type: :system
362
+ end
363
+ ```
364
+
365
+ ## Running Tests
366
+
367
+ ```bash
368
+ # All tests
369
+ bundle exec rspec
370
+
371
+ # Specific file
372
+ bundle exec rspec spec/models/article_spec.rb
373
+
374
+ # Specific test
375
+ bundle exec rspec spec/models/article_spec.rb:25
376
+
377
+ # By type
378
+ bundle exec rspec spec/models
379
+ bundle exec rspec spec/requests
380
+ bundle exec rspec spec/system
381
+
382
+ # With format
383
+ bundle exec rspec --format documentation
384
+ ```
385
+
386
+ ## Best Practices
387
+
388
+ 1. **One assertion per example** when possible
389
+ 2. **Use let/let!** for setup, not before blocks
390
+ 3. **Use traits** for variations
391
+ 4. **Test behavior, not implementation**
392
+ 5. **Use factories, not fixtures**
393
+ 6. **Keep tests fast** - mock external services
@@ -0,0 +1,260 @@
1
+ # Skill: Views
2
+
3
+ ## Purpose
4
+ Create Rails views with ERB, Tailwind CSS, and proper structure following mobile-first design principles.
5
+
6
+ ## View Structure
7
+
8
+ ```
9
+ app/views/
10
+ ├── layouts/
11
+ │ ├── application.html.erb
12
+ │ └── _navigation.html.erb
13
+ ├── shared/
14
+ │ ├── _flash.html.erb
15
+ │ ├── _footer.html.erb
16
+ │ └── _pagination.html.erb
17
+ └── articles/
18
+ ├── index.html.erb
19
+ ├── show.html.erb
20
+ ├── new.html.erb
21
+ ├── edit.html.erb
22
+ ├── _article.html.erb
23
+ └── _form.html.erb
24
+ ```
25
+
26
+ ## Layout Template
27
+
28
+ ```erb
29
+ <!DOCTYPE html>
30
+ <html lang="<%= I18n.locale %>" class="h-full">
31
+ <head>
32
+ <meta charset="UTF-8">
33
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
34
+ <title><%= content_for(:title) || "App Name" %></title>
35
+ <%= csrf_meta_tags %>
36
+ <%= csp_meta_tag %>
37
+ <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
38
+ <%= javascript_importmap_tags %>
39
+ </head>
40
+ <body class="h-full bg-gray-50">
41
+ <%= render "shared/navigation" %>
42
+
43
+ <main class="container mx-auto px-4 py-8">
44
+ <%= render "shared/flash" %>
45
+ <%= yield %>
46
+ </main>
47
+
48
+ <%= render "shared/footer" %>
49
+ </body>
50
+ </html>
51
+ ```
52
+
53
+ ## Flash Messages
54
+
55
+ ```erb
56
+ <%# app/views/shared/_flash.html.erb %>
57
+ <div id="flash" class="fixed top-4 right-4 z-50 space-y-2">
58
+ <% flash.each do |type, message| %>
59
+ <div class="<%= flash_class(type) %> px-4 py-3 rounded-lg shadow-lg flex items-center gap-2"
60
+ data-controller="flash"
61
+ data-flash-target="message">
62
+ <span><%= message %></span>
63
+ <button type="button" data-action="flash#dismiss" class="ml-auto">
64
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
65
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
66
+ </svg>
67
+ </button>
68
+ </div>
69
+ <% end %>
70
+ </div>
71
+ ```
72
+
73
+ ## Index View
74
+
75
+ ```erb
76
+ <%# app/views/articles/index.html.erb %>
77
+ <% content_for :title, "Articles" %>
78
+
79
+ <div class="flex justify-between items-center mb-6">
80
+ <h1 class="text-2xl font-bold text-gray-900">Articles</h1>
81
+ <%= link_to "New Article", new_article_path,
82
+ class: "bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg" %>
83
+ </div>
84
+
85
+ <%= turbo_frame_tag "articles" do %>
86
+ <div class="space-y-4">
87
+ <%= render @articles %>
88
+ </div>
89
+
90
+ <% if @articles.empty? %>
91
+ <div class="text-center py-12 text-gray-500">
92
+ <p>No articles yet.</p>
93
+ <%= link_to "Create your first article", new_article_path, class: "text-blue-600 hover:underline" %>
94
+ </div>
95
+ <% end %>
96
+ <% end %>
97
+
98
+ <%= render "shared/pagination", pagy: @pagy %>
99
+ ```
100
+
101
+ ## Show View
102
+
103
+ ```erb
104
+ <%# app/views/articles/show.html.erb %>
105
+ <% content_for :title, @article.title %>
106
+
107
+ <article class="max-w-3xl mx-auto">
108
+ <header class="mb-8">
109
+ <h1 class="text-3xl font-bold text-gray-900 mb-2"><%= @article.title %></h1>
110
+ <div class="flex items-center gap-4 text-sm text-gray-500">
111
+ <span>By <%= @article.user.name %></span>
112
+ <time datetime="<%= @article.created_at.iso8601 %>">
113
+ <%= @article.created_at.strftime("%B %d, %Y") %>
114
+ </time>
115
+ </div>
116
+ </header>
117
+
118
+ <div class="prose prose-lg max-w-none">
119
+ <%= simple_format(@article.body) %>
120
+ </div>
121
+
122
+ <footer class="mt-8 pt-8 border-t flex gap-4">
123
+ <%= link_to "Edit", edit_article_path(@article), class: "text-blue-600 hover:underline" %>
124
+ <%= button_to "Delete", @article, method: :delete,
125
+ data: { turbo_confirm: "Are you sure?" },
126
+ class: "text-red-600 hover:underline" %>
127
+ <%= link_to "Back", articles_path, class: "text-gray-600 hover:underline" %>
128
+ </footer>
129
+ </article>
130
+ ```
131
+
132
+ ## Form Partial
133
+
134
+ ```erb
135
+ <%# app/views/articles/_form.html.erb %>
136
+ <%= form_with model: article, class: "space-y-6" do |f| %>
137
+ <% if article.errors.any? %>
138
+ <div class="bg-red-50 border border-red-200 rounded-lg p-4">
139
+ <h3 class="text-red-800 font-medium mb-2">Please fix the following errors:</h3>
140
+ <ul class="list-disc list-inside text-red-700 text-sm">
141
+ <% article.errors.full_messages.each do |error| %>
142
+ <li><%= error %></li>
143
+ <% end %>
144
+ </ul>
145
+ </div>
146
+ <% end %>
147
+
148
+ <div>
149
+ <%= f.label :title, class: "block text-sm font-medium text-gray-700 mb-1" %>
150
+ <%= f.text_field :title,
151
+ class: "w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500",
152
+ required: true %>
153
+ </div>
154
+
155
+ <div>
156
+ <%= f.label :body, class: "block text-sm font-medium text-gray-700 mb-1" %>
157
+ <%= f.text_area :body,
158
+ rows: 10,
159
+ class: "w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" %>
160
+ </div>
161
+
162
+ <div class="flex items-center gap-2">
163
+ <%= f.check_box :published, class: "rounded border-gray-300 text-blue-600 focus:ring-blue-500" %>
164
+ <%= f.label :published, "Publish immediately", class: "text-sm text-gray-700" %>
165
+ </div>
166
+
167
+ <div class="flex gap-4">
168
+ <%= f.submit class: "bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg cursor-pointer" %>
169
+ <%= link_to "Cancel", articles_path, class: "text-gray-600 hover:underline py-2" %>
170
+ </div>
171
+ <% end %>
172
+ ```
173
+
174
+ ## Card Partial
175
+
176
+ ```erb
177
+ <%# app/views/articles/_article.html.erb %>
178
+ <%= turbo_frame_tag dom_id(article) do %>
179
+ <article class="bg-white rounded-lg shadow p-6 hover:shadow-md transition-shadow">
180
+ <h2 class="text-xl font-semibold mb-2">
181
+ <%= link_to article.title, article, class: "text-gray-900 hover:text-blue-600" %>
182
+ </h2>
183
+
184
+ <p class="text-gray-600 mb-4 line-clamp-2"><%= truncate(article.body, length: 150) %></p>
185
+
186
+ <div class="flex items-center justify-between text-sm text-gray-500">
187
+ <span><%= article.user.name %></span>
188
+ <time><%= time_ago_in_words(article.created_at) %> ago</time>
189
+ </div>
190
+ </article>
191
+ <% end %>
192
+ ```
193
+
194
+ ## Helper Methods
195
+
196
+ ```ruby
197
+ # app/helpers/application_helper.rb
198
+ module ApplicationHelper
199
+ def flash_class(type)
200
+ case type.to_sym
201
+ when :notice, :success
202
+ "bg-green-100 text-green-800 border border-green-200"
203
+ when :alert, :error
204
+ "bg-red-100 text-red-800 border border-red-200"
205
+ when :warning
206
+ "bg-yellow-100 text-yellow-800 border border-yellow-200"
207
+ else
208
+ "bg-blue-100 text-blue-800 border border-blue-200"
209
+ end
210
+ end
211
+
212
+ def active_link_class(path)
213
+ current_page?(path) ? "text-blue-600 font-medium" : "text-gray-600 hover:text-gray-900"
214
+ end
215
+ end
216
+ ```
217
+
218
+ ## Responsive Design
219
+
220
+ ```erb
221
+ <%# Mobile-first grid %>
222
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
223
+ <%= render @articles %>
224
+ </div>
225
+
226
+ <%# Responsive navigation %>
227
+ <nav class="flex flex-col md:flex-row md:items-center gap-4">
228
+ <%= link_to "Home", root_path %>
229
+ <%= link_to "Articles", articles_path %>
230
+ </nav>
231
+
232
+ <%# Hidden on mobile %>
233
+ <div class="hidden md:block">Desktop only</div>
234
+
235
+ <%# Visible on mobile %>
236
+ <div class="md:hidden">Mobile only</div>
237
+ ```
238
+
239
+ ## Turbo Frames
240
+
241
+ ```erb
242
+ <%# Lazy loading %>
243
+ <%= turbo_frame_tag "comments", src: article_comments_path(@article), loading: :lazy do %>
244
+ <p class="text-gray-500">Loading comments...</p>
245
+ <% end %>
246
+
247
+ <%# Modal frame %>
248
+ <%= turbo_frame_tag "modal" %>
249
+
250
+ <%# Form that updates frame %>
251
+ <%= turbo_frame_tag "search_results" do %>
252
+ <%= form_with url: search_path, method: :get, data: { turbo_frame: "search_results" } do |f| %>
253
+ <%= f.search_field :q, placeholder: "Search..." %>
254
+ <% end %>
255
+
256
+ <div id="results">
257
+ <%= render @results %>
258
+ </div>
259
+ <% end %>
260
+ ```