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,351 @@
1
+ # Skill: File Uploads
2
+
3
+ ## Purpose
4
+ Handle file uploads using Active Storage with local disk storage.
5
+
6
+ ## Setup
7
+
8
+ ### Install Active Storage
9
+ ```bash
10
+ rails active_storage:install
11
+ rails db:migrate
12
+ ```
13
+
14
+ ### Configure Storage
15
+ ```yaml
16
+ # config/storage.yml
17
+ local:
18
+ service: Disk
19
+ root: <%= Rails.root.join("storage") %>
20
+ ```
21
+
22
+ ```ruby
23
+ # config/environments/development.rb
24
+ config.active_storage.service = :local
25
+
26
+ # config/environments/production.rb
27
+ config.active_storage.service = :local # Or cloud service
28
+ ```
29
+
30
+ ## Model Attachments
31
+
32
+ ### Single Attachment
33
+ ```ruby
34
+ class User < ApplicationRecord
35
+ has_one_attached :avatar
36
+
37
+ # With validation
38
+ validates :avatar, content_type: ['image/png', 'image/jpeg'],
39
+ size: { less_than: 5.megabytes }
40
+ end
41
+ ```
42
+
43
+ ### Multiple Attachments
44
+ ```ruby
45
+ class Article < ApplicationRecord
46
+ has_many_attached :images
47
+
48
+ validates :images, content_type: ['image/png', 'image/jpeg', 'image/webp'],
49
+ size: { less_than: 10.megabytes },
50
+ limit: { max: 10 }
51
+ end
52
+ ```
53
+
54
+ ### With Variants (Image Processing)
55
+ ```ruby
56
+ # Gemfile
57
+ gem "image_processing", "~> 1.2"
58
+ ```
59
+
60
+ ```ruby
61
+ class User < ApplicationRecord
62
+ has_one_attached :avatar do |attachable|
63
+ attachable.variant :thumb, resize_to_limit: [100, 100]
64
+ attachable.variant :medium, resize_to_limit: [300, 300]
65
+ attachable.variant :large, resize_to_limit: [800, 800]
66
+ end
67
+ end
68
+ ```
69
+
70
+ ## Controller Handling
71
+
72
+ ### Single File Upload
73
+ ```ruby
74
+ class UsersController < ApplicationController
75
+ def update
76
+ if current_user.update(user_params)
77
+ redirect_to current_user, notice: "Profile updated."
78
+ else
79
+ render :edit, status: :unprocessable_entity
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def user_params
86
+ params.require(:user).permit(:name, :email, :avatar)
87
+ end
88
+ end
89
+ ```
90
+
91
+ ### Multiple File Upload
92
+ ```ruby
93
+ class ArticlesController < ApplicationController
94
+ def create
95
+ @article = current_user.articles.build(article_params)
96
+
97
+ if @article.save
98
+ redirect_to @article
99
+ else
100
+ render :new, status: :unprocessable_entity
101
+ end
102
+ end
103
+
104
+ def update
105
+ @article = Article.find(params[:id])
106
+
107
+ if @article.update(article_params)
108
+ redirect_to @article
109
+ else
110
+ render :edit, status: :unprocessable_entity
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def article_params
117
+ params.require(:article).permit(:title, :body, images: [])
118
+ end
119
+ end
120
+ ```
121
+
122
+ ### Removing Attachments
123
+ ```ruby
124
+ class UsersController < ApplicationController
125
+ def remove_avatar
126
+ current_user.avatar.purge
127
+ redirect_to edit_user_path(current_user), notice: "Avatar removed."
128
+ end
129
+ end
130
+
131
+ # Routes
132
+ resources :users do
133
+ member do
134
+ delete :remove_avatar
135
+ end
136
+ end
137
+ ```
138
+
139
+ ## Form Helpers
140
+
141
+ ### Single File
142
+ ```erb
143
+ <%= form_with model: @user do |f| %>
144
+ <div>
145
+ <%= f.label :avatar %>
146
+ <%= f.file_field :avatar,
147
+ accept: "image/png,image/jpeg",
148
+ class: "block w-full text-sm text-gray-500
149
+ file:mr-4 file:py-2 file:px-4
150
+ file:rounded file:border-0
151
+ file:text-sm file:font-semibold
152
+ file:bg-blue-50 file:text-blue-700
153
+ hover:file:bg-blue-100" %>
154
+ </div>
155
+
156
+ <% if @user.avatar.attached? %>
157
+ <div class="mt-2">
158
+ <%= image_tag @user.avatar.variant(:thumb), class: "rounded" %>
159
+ <%= link_to "Remove", remove_avatar_user_path(@user),
160
+ method: :delete,
161
+ data: { turbo_confirm: "Remove avatar?" },
162
+ class: "text-red-600 text-sm" %>
163
+ </div>
164
+ <% end %>
165
+
166
+ <%= f.submit "Save" %>
167
+ <% end %>
168
+ ```
169
+
170
+ ### Multiple Files
171
+ ```erb
172
+ <%= form_with model: @article do |f| %>
173
+ <div>
174
+ <%= f.label :images %>
175
+ <%= f.file_field :images,
176
+ multiple: true,
177
+ accept: "image/*",
178
+ class: "block w-full" %>
179
+ </div>
180
+
181
+ <% if @article.images.attached? %>
182
+ <div class="grid grid-cols-3 gap-4 mt-4">
183
+ <% @article.images.each do |image| %>
184
+ <div class="relative">
185
+ <%= image_tag image.variant(resize_to_limit: [200, 200]), class: "rounded" %>
186
+ <%= button_to "×",
187
+ purge_image_article_path(@article, image_id: image.id),
188
+ method: :delete,
189
+ class: "absolute top-1 right-1 bg-red-500 text-white rounded-full w-6 h-6" %>
190
+ </div>
191
+ <% end %>
192
+ </div>
193
+ <% end %>
194
+
195
+ <%= f.submit "Save" %>
196
+ <% end %>
197
+ ```
198
+
199
+ ### Direct Upload (JavaScript)
200
+ ```erb
201
+ <%= form_with model: @article, data: { controller: "upload" } do |f| %>
202
+ <%= f.file_field :images,
203
+ multiple: true,
204
+ direct_upload: true,
205
+ data: {
206
+ upload_target: "input",
207
+ action: "change->upload#upload"
208
+ } %>
209
+
210
+ <div data-upload-target="progress" class="hidden">
211
+ <div class="bg-blue-600 h-2 rounded" data-upload-target="bar" style="width: 0%"></div>
212
+ </div>
213
+ <% end %>
214
+ ```
215
+
216
+ ```javascript
217
+ // app/javascript/controllers/upload_controller.js
218
+ import { Controller } from "@hotwired/stimulus"
219
+ import { DirectUpload } from "@rails/activestorage"
220
+
221
+ export default class extends Controller {
222
+ static targets = ["input", "progress", "bar"]
223
+
224
+ upload() {
225
+ Array.from(this.inputTarget.files).forEach(file => {
226
+ const upload = new DirectUpload(file, this.inputTarget.dataset.directUploadUrl, this)
227
+
228
+ upload.create((error, blob) => {
229
+ if (error) {
230
+ console.error(error)
231
+ } else {
232
+ // Append hidden field with signed_id
233
+ const hiddenField = document.createElement("input")
234
+ hiddenField.type = "hidden"
235
+ hiddenField.name = this.inputTarget.name
236
+ hiddenField.value = blob.signed_id
237
+ this.element.appendChild(hiddenField)
238
+ }
239
+ })
240
+ })
241
+ }
242
+
243
+ directUploadWillStoreFileWithXHR(request) {
244
+ this.progressTarget.classList.remove("hidden")
245
+ request.upload.addEventListener("progress", event => {
246
+ const progress = (event.loaded / event.total) * 100
247
+ this.barTarget.style.width = `${progress}%`
248
+ })
249
+ }
250
+ }
251
+ ```
252
+
253
+ ## Displaying Attachments
254
+
255
+ ### Images
256
+ ```erb
257
+ <%# Original %>
258
+ <%= image_tag @user.avatar %>
259
+
260
+ <%# With variant %>
261
+ <%= image_tag @user.avatar.variant(:thumb) %>
262
+
263
+ <%# With fallback %>
264
+ <% if @user.avatar.attached? %>
265
+ <%= image_tag @user.avatar.variant(:medium), class: "rounded-full" %>
266
+ <% else %>
267
+ <%= image_tag "default-avatar.png", class: "rounded-full" %>
268
+ <% end %>
269
+ ```
270
+
271
+ ### Files/Documents
272
+ ```erb
273
+ <% if @document.file.attached? %>
274
+ <div class="flex items-center gap-2">
275
+ <span><%= @document.file.filename %></span>
276
+ <span class="text-gray-500">(<%= number_to_human_size(@document.file.byte_size) %>)</span>
277
+ <%= link_to "Download", rails_blob_path(@document.file, disposition: "attachment"),
278
+ class: "text-blue-600" %>
279
+ </div>
280
+ <% end %>
281
+ ```
282
+
283
+ ### PDF Preview
284
+ ```erb
285
+ <% if @document.file.content_type == "application/pdf" %>
286
+ <iframe src="<%= rails_blob_path(@document.file) %>"
287
+ class="w-full h-96 border rounded"></iframe>
288
+ <% end %>
289
+ ```
290
+
291
+ ## Validations
292
+
293
+ ### Custom Validator
294
+ ```ruby
295
+ # app/validators/content_type_validator.rb
296
+ class ContentTypeValidator < ActiveModel::EachValidator
297
+ def validate_each(record, attribute, value)
298
+ return unless value.attached?
299
+
300
+ allowed = Array(options[:in] || options[:with])
301
+
302
+ files = value.is_a?(ActiveStorage::Attached::Many) ? value : [value]
303
+
304
+ files.each do |file|
305
+ unless allowed.include?(file.content_type)
306
+ record.errors.add(attribute, :invalid_content_type)
307
+ end
308
+ end
309
+ end
310
+ end
311
+ ```
312
+
313
+ ### Usage
314
+ ```ruby
315
+ class Article < ApplicationRecord
316
+ has_many_attached :images
317
+
318
+ validates :images, content_type: { in: %w[image/png image/jpeg image/webp] }
319
+ validates :images, size: { less_than: 5.megabytes }
320
+ end
321
+ ```
322
+
323
+ ## Background Processing
324
+
325
+ ### Analyze on Upload
326
+ ```ruby
327
+ # Active Storage analyzes files automatically in background
328
+ # Configure in config/application.rb
329
+ config.active_storage.queues.analysis = :default
330
+ config.active_storage.queues.purge = :default
331
+ ```
332
+
333
+ ### Custom Processing Job
334
+ ```ruby
335
+ class ProcessImageJob < ApplicationJob
336
+ queue_as :default
337
+
338
+ def perform(image_id)
339
+ image = ActiveStorage::Blob.find(image_id)
340
+ # Custom processing
341
+ end
342
+ end
343
+ ```
344
+
345
+ ## Best Practices
346
+
347
+ 1. **Validate content types** - Prevent malicious uploads
348
+ 2. **Set size limits** - Protect storage and bandwidth
349
+ 3. **Use variants** - Don't serve original large images
350
+ 4. **Enable direct uploads** - Better UX for large files
351
+ 5. **Purge unused files** - Clean up orphaned attachments
@@ -0,0 +1,296 @@
1
+ # Skill: Hotwire Native
2
+
3
+ ## Purpose
4
+ Build native iOS and Android apps using the existing Rails views with Hotwire Native (formerly Turbo Native).
5
+
6
+ ## Overview
7
+
8
+ Hotwire Native wraps your Rails web app in a native shell, providing:
9
+ - Native navigation (push/pop)
10
+ - Native UI elements (tab bars, toolbars)
11
+ - Bridge components for native features
12
+ - Offline support
13
+ - Push notifications
14
+
15
+ ## iOS Setup
16
+
17
+ ### Project Structure
18
+ ```
19
+ ios/
20
+ ├── App/
21
+ │ ├── AppDelegate.swift
22
+ │ ├── SceneDelegate.swift
23
+ │ └── Configuration.swift
24
+ ├── Navigation/
25
+ │ ├── Navigator.swift
26
+ │ └── PathConfiguration.swift
27
+ └── Bridge/
28
+ └── Components/
29
+ ```
30
+
31
+ ### Basic Configuration
32
+ ```swift
33
+ // Configuration.swift
34
+ import HotwireNative
35
+
36
+ struct Configuration {
37
+ static let baseURL = URL(string: "https://yourapp.com")!
38
+
39
+ static var pathConfiguration: PathConfiguration {
40
+ PathConfiguration(sources: [
41
+ .server(baseURL.appendingPathComponent("/configurations/ios_v1.json"))
42
+ ])
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### Scene Delegate
48
+ ```swift
49
+ // SceneDelegate.swift
50
+ import UIKit
51
+ import HotwireNative
52
+
53
+ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
54
+ var window: UIWindow?
55
+ private let navigator = Navigator()
56
+
57
+ func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options: UIScene.ConnectionOptions) {
58
+ guard let windowScene = scene as? UIWindowScene else { return }
59
+
60
+ window = UIWindow(windowScene: windowScene)
61
+ window?.rootViewController = navigator.rootViewController
62
+ window?.makeKeyAndVisible()
63
+
64
+ navigator.route(Configuration.baseURL)
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## Android Setup
70
+
71
+ ### Project Structure
72
+ ```
73
+ android/
74
+ ├── app/src/main/
75
+ │ ├── java/com/yourapp/
76
+ │ │ ├── MainActivity.kt
77
+ │ │ ├── MainSessionNavHostFragment.kt
78
+ │ │ └── bridge/
79
+ │ └── res/
80
+ │ └── navigation/
81
+ │ └── main.xml
82
+ ```
83
+
84
+ ### Main Activity
85
+ ```kotlin
86
+ // MainActivity.kt
87
+ import android.os.Bundle
88
+ import dev.hotwire.navigation.activities.HotwireActivity
89
+ import dev.hotwire.navigation.navigator.NavigatorConfiguration
90
+
91
+ class MainActivity : HotwireActivity() {
92
+ override fun onCreate(savedInstanceState: Bundle?) {
93
+ super.onCreate(savedInstanceState)
94
+ setContentView(R.layout.activity_main)
95
+ }
96
+
97
+ override fun navigatorConfigurations() = listOf(
98
+ NavigatorConfiguration(
99
+ name = "main",
100
+ startLocation = "https://yourapp.com",
101
+ navigatorHostId = R.id.main_nav_host
102
+ )
103
+ )
104
+ }
105
+ ```
106
+
107
+ ## Path Configuration
108
+
109
+ ### Server-Side JSON
110
+ ```json
111
+ // public/configurations/ios_v1.json
112
+ {
113
+ "settings": {
114
+ "screenshots_enabled": true
115
+ },
116
+ "rules": [
117
+ {
118
+ "patterns": ["/"],
119
+ "properties": {
120
+ "presentation": "default"
121
+ }
122
+ },
123
+ {
124
+ "patterns": ["/new$", "/edit$"],
125
+ "properties": {
126
+ "presentation": "modal"
127
+ }
128
+ },
129
+ {
130
+ "patterns": ["/articles/\\d+"],
131
+ "properties": {
132
+ "presentation": "push"
133
+ }
134
+ }
135
+ ]
136
+ }
137
+ ```
138
+
139
+ ### Presentation Types
140
+ - `default` - Standard push navigation
141
+ - `modal` - Present as modal sheet
142
+ - `replace` - Replace current screen
143
+ - `pop` - Pop back
144
+ - `refresh` - Refresh current
145
+ - `none` - Handle in native code
146
+
147
+ ## Bridge Components
148
+
149
+ ### Rails Helper
150
+ ```ruby
151
+ # app/helpers/hotwire_native_helper.rb
152
+ module HotwireNativeHelper
153
+ def hotwire_native_app?
154
+ request.user_agent&.include?("Hotwire Native")
155
+ end
156
+
157
+ def hotwire_native_ios?
158
+ request.user_agent&.include?("Hotwire Native iOS")
159
+ end
160
+
161
+ def hotwire_native_android?
162
+ request.user_agent&.include?("Hotwire Native Android")
163
+ end
164
+ end
165
+ ```
166
+
167
+ ### Conditional Rendering
168
+ ```erb
169
+ <% if hotwire_native_app? %>
170
+ <%# Native-specific UI %>
171
+ <div data-bridge-component="navbar" data-bridge-title="<%= @article.title %>"></div>
172
+ <% else %>
173
+ <%# Web navigation %>
174
+ <%= render "shared/navigation" %>
175
+ <% end %>
176
+ ```
177
+
178
+ ### Bridge Component (JavaScript)
179
+ ```javascript
180
+ // app/javascript/controllers/bridge/button_controller.js
181
+ import { BridgeComponent } from "@hotwired/hotwire-native-bridge"
182
+
183
+ export default class extends BridgeComponent {
184
+ static component = "button"
185
+ static targets = ["button"]
186
+
187
+ connect() {
188
+ super.connect()
189
+ this.send("connect", { title: this.buttonTarget.textContent })
190
+ }
191
+
192
+ // Called from native
193
+ clicked() {
194
+ this.buttonTarget.click()
195
+ }
196
+ }
197
+ ```
198
+
199
+ ### iOS Bridge Handler
200
+ ```swift
201
+ // ButtonComponent.swift
202
+ import HotwireNative
203
+
204
+ final class ButtonComponent: BridgeComponent {
205
+ override class var name: String { "button" }
206
+
207
+ override func onReceive(message: Message) {
208
+ guard let event = Event(rawValue: message.event) else { return }
209
+
210
+ switch event {
211
+ case .connect:
212
+ handleConnect(message: message)
213
+ }
214
+ }
215
+
216
+ private func handleConnect(message: Message) {
217
+ guard let data: ConnectData = message.data() else { return }
218
+ // Add native button to toolbar
219
+ addButton(title: data.title)
220
+ }
221
+
222
+ private func addButton(title: String) {
223
+ let button = UIBarButtonItem(title: title, style: .plain, target: self, action: #selector(buttonTapped))
224
+ delegate?.webView?.viewController?.navigationItem.rightBarButtonItem = button
225
+ }
226
+
227
+ @objc private func buttonTapped() {
228
+ reply(to: "connect", with: MessageData(action: "clicked"))
229
+ }
230
+ }
231
+
232
+ private extension ButtonComponent {
233
+ enum Event: String {
234
+ case connect
235
+ }
236
+
237
+ struct ConnectData: Decodable {
238
+ let title: String
239
+ }
240
+ }
241
+ ```
242
+
243
+ ## Native Features
244
+
245
+ ### Pull to Refresh
246
+ ```erb
247
+ <%# Enable in layout %>
248
+ <body data-turbo-refresh-method="morph" data-turbo-refresh-scroll="preserve">
249
+ ```
250
+
251
+ ### Native Form Inputs
252
+ ```erb
253
+ <% if hotwire_native_app? %>
254
+ <%= f.date_field :date, data: { bridge_component: "date-picker" } %>
255
+ <% else %>
256
+ <%= f.date_field :date %>
257
+ <% end %>
258
+ ```
259
+
260
+ ### Camera/Photos
261
+ ```erb
262
+ <div data-controller="bridge--photo" data-bridge-component="photo">
263
+ <button data-action="bridge--photo#capture">Take Photo</button>
264
+ <input type="hidden" name="photo_data" data-bridge--photo-target="input">
265
+ </div>
266
+ ```
267
+
268
+ ## Styling for Native
269
+
270
+ ```erb
271
+ <%# app/views/layouts/application.html.erb %>
272
+ <html class="<%= 'hotwire-native' if hotwire_native_app? %>">
273
+
274
+ <%# In CSS %>
275
+ <style>
276
+ /* Hide web-only elements in native */
277
+ .hotwire-native .web-only {
278
+ display: none;
279
+ }
280
+
281
+ /* Safe area insets */
282
+ .hotwire-native body {
283
+ padding-top: env(safe-area-inset-top);
284
+ padding-bottom: env(safe-area-inset-bottom);
285
+ }
286
+ </style>
287
+ ```
288
+
289
+ ## Best Practices
290
+
291
+ 1. **Mobile-first design** - Views should work well on small screens
292
+ 2. **Use semantic HTML** - Native can style based on elements
293
+ 3. **Minimize JavaScript** - Let native handle interactions
294
+ 4. **Test on devices** - Simulator ≠ real device
295
+ 5. **Handle offline** - Show appropriate messages
296
+ 6. **Fast responses** - Native users expect instant feedback