nitro_kit 2.0.0.alpha.2 → 2.0.0.alpha.4

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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +229 -1
  3. data/README.md +25 -10
  4. data/STYLE_GUIDE.md +47 -5
  5. data/app/assets/stylesheets/nitro_kit-tailwind-v4.css +3 -3
  6. data/app/assets/stylesheets/nitro_kit.css +2820 -1816
  7. data/app/components/nitro_kit/accordion.rb +2 -26
  8. data/app/components/nitro_kit/alert.rb +1 -11
  9. data/app/components/nitro_kit/app_navigation.rb +45 -11
  10. data/app/components/nitro_kit/app_shell.rb +1 -7
  11. data/app/components/nitro_kit/appearance_picker.rb +11 -12
  12. data/app/components/nitro_kit/avatar.rb +6 -1
  13. data/app/components/nitro_kit/avatar_stack.rb +3 -0
  14. data/app/components/nitro_kit/badge.rb +13 -4
  15. data/app/components/nitro_kit/button_group.rb +2 -2
  16. data/app/components/nitro_kit/checkbox.rb +7 -4
  17. data/app/components/nitro_kit/checkbox_group.rb +0 -25
  18. data/app/components/nitro_kit/combobox.rb +4 -33
  19. data/app/components/nitro_kit/command_palette.rb +42 -59
  20. data/app/components/nitro_kit/component.rb +57 -5
  21. data/app/components/nitro_kit/danger_zone.rb +5 -1
  22. data/app/components/nitro_kit/data_section.rb +28 -4
  23. data/app/components/nitro_kit/details_table.rb +19 -8
  24. data/app/components/nitro_kit/dialog.rb +23 -19
  25. data/app/components/nitro_kit/dropdown.rb +1 -6
  26. data/app/components/nitro_kit/dropzone.rb +4 -15
  27. data/app/components/nitro_kit/empty_state.rb +21 -2
  28. data/app/components/nitro_kit/field.rb +35 -11
  29. data/app/components/nitro_kit/form_builder.rb +1 -2
  30. data/app/components/nitro_kit/progressive_image.rb +2 -2
  31. data/app/components/nitro_kit/radio_button.rb +7 -4
  32. data/app/components/nitro_kit/radio_button_group.rb +0 -25
  33. data/app/components/nitro_kit/select.rb +4 -17
  34. data/app/components/nitro_kit/settings_layout.rb +17 -4
  35. data/app/components/nitro_kit/{form_section.rb → settings_section.rb} +18 -11
  36. data/app/components/nitro_kit/sheet.rb +29 -20
  37. data/app/components/nitro_kit/stat_grid.rb +18 -2
  38. data/app/components/nitro_kit/table.rb +22 -3
  39. data/app/components/nitro_kit/tabs.rb +2 -13
  40. data/app/components/nitro_kit/toast.rb +4 -2
  41. data/app/components/nitro_kit/toolbar.rb +2 -0
  42. data/app/components/nitro_kit/tooltip.rb +7 -10
  43. data/app/javascript/controllers/nk/app_shell_controller.js +3 -3
  44. data/app/javascript/controllers/nk/appearance_controller.js +6 -0
  45. data/app/javascript/controllers/nk/combobox_controller.js +51 -1
  46. data/app/javascript/controllers/nk/command_palette_controller.js +25 -11
  47. data/app/javascript/controllers/nk/dialog_controller.js +54 -0
  48. data/app/javascript/controllers/nk/dropdown_controller.js +45 -0
  49. data/app/javascript/controllers/nk/dropzone_controller.js +2 -2
  50. data/app/javascript/controllers/nk/progressive_image_controller.js +11 -7
  51. data/app/javascript/controllers/nk/tabs_controller.js +21 -2
  52. data/app/javascript/controllers/nk/tooltip_controller.js +9 -0
  53. data/config/locales/en.yml +4 -1
  54. data/docs/agent_guide.md +63 -123
  55. data/docs/agent_native_spec.md +72 -383
  56. data/docs/browser_support.md +82 -0
  57. data/docs/component_contracts.md +108 -82
  58. data/docs/customization.md +689 -91
  59. data/docs/hotwire.md +50 -76
  60. data/docs/initialization_prompt.md +26 -31
  61. data/docs/migration_1_to_2.md +88 -30
  62. data/docs/patterns/application_foundation.md +45 -119
  63. data/docs/patterns/crud_resource.md +44 -126
  64. data/docs/patterns/destructive_action.md +45 -77
  65. data/docs/patterns/flash_and_toast.md +18 -36
  66. data/docs/patterns/inline_edit.md +21 -44
  67. data/docs/patterns/queryable_collection.md +65 -161
  68. data/docs/patterns/resource_form.md +33 -81
  69. data/docs/rails_conventions.md +38 -75
  70. data/docs/rails_integration.md +96 -446
  71. data/lib/generators/nitro_kit/upgrade_smoke_tests_generator.rb +12 -0
  72. data/lib/nitro_kit/installation.rb +20 -7
  73. data/lib/nitro_kit/migration_inventory.rb +275 -8
  74. data/lib/nitro_kit/upgrade_smoke_test.rb +10 -12
  75. data/lib/nitro_kit/version.rb +1 -1
  76. data/lib/rails/commands/nitro_kit/nitro_kit_command.rb +10 -3
  77. data/plugins/nitro-kit/skills/nitro-kit-hotwire/SKILL.md +4 -0
  78. data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +13 -1
  79. data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +32 -15
  80. data/src/stylesheets/nitro_kit/components/accordion.css +44 -44
  81. data/src/stylesheets/nitro_kit/components/alert.css +27 -36
  82. data/src/stylesheets/nitro_kit/components/app_navigation.css +92 -20
  83. data/src/stylesheets/nitro_kit/components/app_shell.css +199 -196
  84. data/src/stylesheets/nitro_kit/components/appearance_picker.css +61 -39
  85. data/src/stylesheets/nitro_kit/components/auth_shell.css +2 -2
  86. data/src/stylesheets/nitro_kit/components/avatar.css +15 -11
  87. data/src/stylesheets/nitro_kit/components/avatar_stack.css +48 -29
  88. data/src/stylesheets/nitro_kit/components/badge.css +13 -9
  89. data/src/stylesheets/nitro_kit/components/button.css +121 -79
  90. data/src/stylesheets/nitro_kit/components/button_group.css +19 -14
  91. data/src/stylesheets/nitro_kit/components/card.css +12 -6
  92. data/src/stylesheets/nitro_kit/components/checkbox.css +54 -67
  93. data/src/stylesheets/nitro_kit/components/checkbox_group.css +16 -16
  94. data/src/stylesheets/nitro_kit/components/combobox.css +39 -29
  95. data/src/stylesheets/nitro_kit/components/command_palette.css +110 -51
  96. data/src/stylesheets/nitro_kit/components/container.css +6 -6
  97. data/src/stylesheets/nitro_kit/components/control_group.css +61 -67
  98. data/src/stylesheets/nitro_kit/components/danger_zone.css +8 -8
  99. data/src/stylesheets/nitro_kit/components/data_section.css +4 -4
  100. data/src/stylesheets/nitro_kit/components/details_table.css +17 -15
  101. data/src/stylesheets/nitro_kit/components/dialog.css +8 -5
  102. data/src/stylesheets/nitro_kit/components/dropdown.css +36 -30
  103. data/src/stylesheets/nitro_kit/components/dropzone.css +78 -35
  104. data/src/stylesheets/nitro_kit/components/empty_state.css +7 -7
  105. data/src/stylesheets/nitro_kit/components/field.css +32 -28
  106. data/src/stylesheets/nitro_kit/components/field_group.css +13 -0
  107. data/src/stylesheets/nitro_kit/components/fieldset.css +8 -2
  108. data/src/stylesheets/nitro_kit/components/flex.css +1 -1
  109. data/src/stylesheets/nitro_kit/components/grid.css +1 -1
  110. data/src/stylesheets/nitro_kit/components/icon.css +5 -5
  111. data/src/stylesheets/nitro_kit/components/input.css +42 -8
  112. data/src/stylesheets/nitro_kit/components/layout.css +165 -165
  113. data/src/stylesheets/nitro_kit/components/page_header.css +7 -7
  114. data/src/stylesheets/nitro_kit/components/pagination.css +58 -49
  115. data/src/stylesheets/nitro_kit/components/pagination_bar.css +9 -9
  116. data/src/stylesheets/nitro_kit/components/palette.css +145 -77
  117. data/src/stylesheets/nitro_kit/components/progressive_image.css +45 -55
  118. data/src/stylesheets/nitro_kit/components/radio_button.css +48 -46
  119. data/src/stylesheets/nitro_kit/components/radio_button_group.css +49 -49
  120. data/src/stylesheets/nitro_kit/components/rich_text_area.css +16 -1
  121. data/src/stylesheets/nitro_kit/components/select.css +24 -15
  122. data/src/stylesheets/nitro_kit/components/settings_layout.css +26 -23
  123. data/src/stylesheets/nitro_kit/components/settings_section.css +84 -0
  124. data/src/stylesheets/nitro_kit/components/sheet.css +30 -10
  125. data/src/stylesheets/nitro_kit/components/stat_grid.css +11 -10
  126. data/src/stylesheets/nitro_kit/components/switch.css +60 -62
  127. data/src/stylesheets/nitro_kit/components/table.css +43 -35
  128. data/src/stylesheets/nitro_kit/components/tabs.css +48 -40
  129. data/src/stylesheets/nitro_kit/components/textarea.css +20 -5
  130. data/src/stylesheets/nitro_kit/components/toast.css +11 -47
  131. data/src/stylesheets/nitro_kit/components/toolbar.css +16 -41
  132. data/src/stylesheets/nitro_kit/components/tooltip.css +38 -29
  133. data/src/stylesheets/nitro_kit/components/typeset.css +150 -4
  134. data/src/stylesheets/nitro_kit/reset.css +15 -15
  135. data/src/stylesheets/nitro_kit/tokens.css +501 -79
  136. metadata +4 -4
  137. data/docs/new_app_strategy.md +0 -22
  138. data/src/stylesheets/nitro_kit/components/form_section.css +0 -71
@@ -1,144 +1,62 @@
1
- # A complete product resource
1
+ # Complete product resource
2
2
 
3
- Build CRUD as one coherent product surface, not independent generated screens.
4
- Before writing the views, name the resource, tenant boundary, actor, lifecycle,
5
- visibility rules, and states. Implement index, form, detail, destructive action,
6
- and their tests together.
3
+ **Audience:** Coding agents and developers implementing a full Rails CRUD
4
+ resource with Nitro Kit.
7
5
 
8
6
  ## Summary
9
7
 
10
- - Build index, form, detail, destructive action, and their tests as one
11
- surface; name the resource, tenant boundary, actor, lifecycle, and states
12
- before writing views.
13
- - `AppShell(layout: :hybrid)` frames the admin area, and a `Toolbar` in
14
- `shell.topbar` carries the route's single `h1` plus its basic actions.
15
- - Spend hierarchy once: one route, one `h1`, an `h2` only for a genuinely
16
- separate region, and no resource name repeated across toolbar, `PageHeader`,
17
- `DataSection`, Card, and caption.
18
- - Child routes add one compact icon Back link before the title instead of a
19
- trailing Cancel action.
20
- - One responsive padding rule on the main wrapper owns the page gutter;
21
- data-heavy hybrid CRUD content uses the full available width.
8
+ - Define the resource, tenant boundary, actor, lifecycle, visibility, and
9
+ states before writing views.
10
+ - Build index, form, detail, destructive action, and tests as one product
11
+ surface.
12
+ - Use one shell toolbar title and one application-owned page gutter; do not
13
+ repeat hierarchy across nested components.
14
+ - Scope every lookup through the current tenant and model meaningful lifecycle
15
+ transitions as noun resources.
22
16
 
23
- ## Use the hybrid application frame
17
+ ## Resource map
24
18
 
25
- An authenticated admin area defaults to `AppShell(layout: :hybrid)`. Nitro Kit
26
- owns the desktop sidebar, mobile menu button, navigation dialog, focus behavior,
27
- and responsive transition. The application provides destinations and current
28
- state.
19
+ Use `AppShell(layout: :hybrid)` for an authenticated product area. Put the
20
+ route's one `h1` and persistent actions in the topbar `Toolbar`. Child routes
21
+ place one compact Back link before the title. One wrapper inside `shell.main`
22
+ owns page padding; child pages add no outer gutter.
29
23
 
30
- Put a `Toolbar` in `shell.topbar`. Its leading region contains the route's one
31
- `h1`; its trailing region contains basic actions such as New, Edit, Cancel,
32
- Save, Publish, or View. A Button outside a form can submit it through the
33
- native `form:` attribute. This keeps the same action hierarchy on narrow and
34
- wide screens without custom JavaScript.
24
+ | Route | Composition |
25
+ | --------------------- | ----------------------------------------------------------------------------------------------------------------------- |
26
+ | Index | Optional short introduction, then Table or EmptyState and pagination. Use DataSection only for multiple named datasets. |
27
+ | New/Edit | One `SettingsSection` and one shared form component. A toolbar submit targets the form's stable `form:` ID. |
28
+ | Show | Status or metadata, then the resource. Keep lifecycle actions in the normal detail flow. |
29
+ | Edit destructive area | One `DangerZone` with a safe escape. Do not put permanent deletion on every show page. |
35
30
 
36
- ```ruby
37
- AppShell(id: "admin", layout: :hybrid) do |shell|
38
- shell.navigation { render admin_navigation }
39
- shell.topbar do
40
- Toolbar do |toolbar|
41
- toolbar.leading { h1 { page_title } }
42
- toolbar.trailing do
43
- Button(
44
- "Save",
45
- type: :submit,
46
- form: dom_id(@post, :form),
47
- variant: :primary
48
- )
49
- end
50
- end
51
- end
52
- shell.main do
53
- div(data: { ui: "admin-main" }) do
54
- render page
55
- end
56
- end
57
- end
58
- ```
59
-
60
- Child routes add one compact Back link before the title. Prefer an icon-only
61
- Button with an explicit label such as `aria: { label: "Back to projects" }`.
62
- Do not repeat that navigation as a trailing Cancel action.
63
-
64
- The application stylesheet gives `admin-main` one responsive padding rule.
65
- Child pages do not add another outer gutter. Keep data-heavy hybrid CRUD
66
- content full width; constrain only a specific content-led region whose measure
67
- benefits from it.
68
-
69
- Do not add viewport height or another outer padding rule to `admin-main`; the
70
- shell owns viewport geometry and the wrapper owns the one page gutter. Use the
71
- same shell and gutter on team administration and settings routes.
72
- Place a bottom-anchored Settings destination after `AppNavigation#spacer`, then
73
- compose settings subsections with `SettingsLayout` and plain `FormSection`
74
- regions. Settings destinations are links with `aria-current`, not action
75
- Buttons. Read `application_foundation.md` for the complete application frame.
31
+ Use one primary action. Do not render the same Save or Create action in both
32
+ the toolbar and form body. Use Card only for a bounded object that benefits
33
+ from its own surface.
76
34
 
77
- ## Spend hierarchy once
35
+ See [Resource form](resource_form.md),
36
+ [Destructive action](destructive_action.md), and
37
+ [Queryable collection](queryable_collection.md) for complete interaction
38
+ contracts.
78
39
 
79
- - One route, one `h1`, normally in the shell toolbar.
80
- - Add an `h2` only for a genuinely separate region.
81
- - Do not repeat “Posts” in the toolbar, `PageHeader`, `DataSection`, Card, and
82
- visible table caption.
83
- - Use `PageHeader` for a content-led introduction, not as mandatory CRUD
84
- ceremony under an existing toolbar.
85
- - Prefer whitespace and dividers. Use Card only for a bounded object that
86
- benefits from its own surface. Never default to Card inside Card.
40
+ ## Lifecycle and responses
87
41
 
88
- An index begins with a short introduction only if it adds useful context, then
89
- renders its table and pagination directly. Use `DataSection` when a page has
90
- multiple independently named datasets. At zero records, replace the data region
91
- with one intentional `EmptyState`; keep the primary New action in the toolbar.
92
-
93
- A new or edit page begins with `FormSection`. Use one form component for new,
94
- edit, and invalid renders. Put the primary submit in the toolbar by setting the
95
- button's `form:` to the form's stable DOM ID. Invalid submissions render the
96
- same model and form with `422 Unprocessable Entity`. The toolbar owns that
97
- action: do not render a second Save or Create submit inside the form body.
98
-
99
- A detail page begins with status or stable metadata, then the resource itself.
100
- Keep status inside that normal details flow instead of detaching it into a
101
- second side panel.
102
- Use the authenticated `show` route as the operational detail or draft preview.
103
- Put lifecycle forms in the page and associate their toolbar buttons with
104
- `form:`. Put destructive confirmation in one separate `DangerZone` on edit,
105
- with a safe escape back to the record. Do not make every show page end in a
106
- large deletion surface.
107
-
108
- ## Model and route the lifecycle
109
-
110
- Scope every lookup through `Current.team` or `Current.account`. Record
111
- `Current.user` as author, creator, or publisher. If a state has provenance,
112
- timing, or behavior, model it as a record and expose it as a noun resource:
42
+ Scope lookups through `Current.team` or `Current.account`. Use
43
+ `Current.user` as actor. When state has timing, provenance, or behavior, model
44
+ it as a noun resource:
113
45
 
114
46
  ```ruby
115
- namespace :admin do
116
- resources :posts do
117
- resource :publication,
118
- only: %i[create destroy],
119
- module: :posts
120
- end
47
+ resources :posts do
48
+ resource :publication, only: %i[create destroy], module: :posts
121
49
  end
122
50
  ```
123
51
 
124
- The main controller keeps the seven REST actions. Successful mutations redirect
125
- with `303 See Other`. Publication create and destroy invoke domain methods and
126
- redirect. Public controllers query published records only. Ordinary Rails forms
127
- and Turbo Drive are the default; do not add fetch code for CRUD submissions.
128
-
129
- ## Ship the acceptance path
130
-
131
- Request tests cover tenant isolation, public visibility, successful `303`
132
- redirects, invalid `422` renders, pagination, and lifecycle resources. Rendering
133
- assertions should also protect the high-level composition: hybrid AppShell,
134
- one `h1`, navigation, toolbar action, actual form association, table or empty
135
- state, and destructive confirmation. Add one browser test for the meaningful
136
- end-to-end path, using Capybara waiting assertions instead of sleeps.
52
+ Keep the main controller to REST actions. Successful mutations redirect with
53
+ `303 See Other`; invalid forms render the same model with `422 Unprocessable
54
+ Entity`. Public controllers query only publicly visible records.
137
55
 
138
- Assert that each primary action has one visible control. A toolbar-associated
139
- form submit plus an identical body submit is duplication, even when both invoke
140
- the same valid form.
56
+ ## Acceptance checklist
141
57
 
142
- Before finishing, inspect a populated index, empty index, invalid form, narrow
143
- form, draft detail, published detail, and edit-owned destructive dialog. Remove any extra
144
- heading, surface, wrapper, or page gutter that does not communicate information.
58
+ Test tenant isolation, authorization, public visibility, lifecycle resources,
59
+ pagination, `303` redirects, and `422` validation. Protect the high-level
60
+ composition: one title, one primary action, the correct form association,
61
+ Table or EmptyState, and edit-owned destructive confirmation. Inspect
62
+ populated, empty, invalid, narrow, draft, published, and destructive states.
@@ -1,77 +1,58 @@
1
1
  # Destructive action
2
2
 
3
- Use a native Nitro Dialog when the user needs to review impact or type confirmation. The dialog only owns accessible disclosure and focus behavior; a real Rails form owns the destructive request.
3
+ **Audience:** Coding agents and developers implementing delete, revoke,
4
+ archive, or similarly destructive Rails actions.
4
5
 
5
6
  ## Summary
6
7
 
7
- - Use a native Nitro `Dialog` only when the user must review impact or type a
8
- confirmation; a real Rails form owns the destructive request.
9
- - Place the reviewed deletion on the resource's edit page, not on the
10
- operational show page.
11
- - The delete form targets `_top` so a successful redirect navigates the page
12
- instead of resolving inside the surrounding frame.
13
- - Authorize and load the record on the server. The confirmation UI is not a
14
- security boundary.
15
- - When the consequence fits in one sentence, use Turbo's native `turbo_confirm`
16
- instead. Never stack both confirmation surfaces.
8
+ - Use `NitroKit::Dialog` when the user must review impact or type confirmation;
9
+ use Turbo's native browser confirmation for a one-sentence consequence.
10
+ - A real Rails form owns the request, and the server owns authorization.
11
+ - Put permanent deletion on the edit route, not the operational show route.
12
+ - Use a server-rendered review route when confirmation must work without
13
+ JavaScript or Invoker Commands.
17
14
 
18
- ## Reviewed deletion
15
+ ## Choose one confirmation path
19
16
 
20
- Place this composition on the resource's edit page. A show page is the
21
- operational home; it should not advertise permanent deletion on every visit.
17
+ | Need | Pattern |
18
+ | ----------------------------------------------- | --------------------------------------------------------------- |
19
+ | One-sentence confirmation | Real form with `data: { turbo_confirm: "Delete permanently?" }` |
20
+ | Reviewed impact or typed confirmation | `DangerZone` containing a Dialog and real form |
21
+ | Confirmation required without client JavaScript | Ordinary link to a server-rendered review page |
22
22
 
23
- ```ruby
24
- module UI
25
- class DeleteProject < Phlex::HTML
26
- include Phlex::Rails::Helpers::DOMID
27
- include Phlex::Rails::Helpers::FormWith
28
- include Phlex::Rails::Helpers::Routes
29
-
30
- def initialize(project)
31
- @project = project
32
- end
23
+ Never stack `turbo_confirm` inside a Dialog.
33
24
 
34
- def view_template
35
- render NitroKit::DangerZone.new(
36
- title: "Delete project",
37
- description: "This permanently removes the project and its activity.",
38
- id: dom_id(project, :danger_zone)
39
- ) do |zone|
40
- zone.confirmation do
41
- render NitroKit::Dialog.new(id: dom_id(project, :delete_dialog)) do |dialog|
42
- dialog.trigger("Review deletion", variant: :destructive)
43
- dialog.panel(
44
- title: "Delete #{project.name}?",
45
- description: "This action cannot be undone."
46
- ) do
47
- form_with(
48
- model: project,
49
- url: project_path(project),
50
- method: :delete,
51
- data: { turbo_frame: "_top" }
52
- ) do
53
- render NitroKit::Button.new(
54
- "Delete project",
55
- type: :submit,
56
- variant: :destructive,
57
- data: { turbo_submits_with: "Deleting…" }
58
- )
59
- end
60
- dialog.close_button(label: "Cancel deletion")
61
- end
62
- end
25
+ ```ruby
26
+ render NitroKit::DangerZone.new(
27
+ title: "Delete project",
28
+ description: "This permanently removes the project.",
29
+ id: dom_id(project, :danger_zone)
30
+ ) do |zone|
31
+ zone.confirmation do
32
+ render NitroKit::Dialog.new(id: dom_id(project, :delete_dialog)) do |dialog|
33
+ dialog.trigger("Review deletion", variant: :destructive)
34
+ dialog.panel(title: "Delete #{project.name}?") do
35
+ form_with(
36
+ model: project,
37
+ method: :delete,
38
+ data: { turbo_frame: "_top" }
39
+ ) do
40
+ render NitroKit::Button.new(
41
+ "Delete project",
42
+ type: :submit,
43
+ variant: :destructive
44
+ )
63
45
  end
64
- zone.escape NitroKit::Button.new("Keep project", href: project_path(project))
46
+ dialog.close_button(label: "Cancel deletion")
65
47
  end
66
48
  end
67
-
68
- private
69
- attr_reader :project
70
49
  end
50
+ zone.escape NitroKit::Button.new("Keep project", href: project_path(project))
71
51
  end
72
52
  ```
73
53
 
74
- The top-level target makes a successful redirect navigate the page rather than trying to render the destination inside the surrounding settings frame.
54
+ The `_top` target keeps the redirect out of a surrounding frame. The dialog is
55
+ not a security boundary; load and authorize the record on the server.
75
56
 
76
57
  ```ruby
77
58
  def destroy
@@ -81,25 +62,12 @@ def destroy
81
62
  end
82
63
  ```
83
64
 
84
- Authorize and load the record on the server even when the dialog is open. The confirmation UI is not a security boundary.
85
-
86
- ## Compact confirmation
87
-
88
- When the consequence fits in one sentence and needs no review UI, keep the ordinary request and use Turbo's native browser confirmation:
89
-
90
- ```ruby
91
- form_with(model: project, url: project_path(project), method: :delete) do
92
- render NitroKit::Button.new(
93
- "Delete",
94
- type: :submit,
95
- variant: :destructive,
96
- data: { turbo_confirm: "Delete this project permanently?" }
97
- )
98
- end
99
- ```
100
-
101
- Do not stack `turbo_confirm` inside a Dialog. Choose one confirmation surface.
65
+ Without JavaScript, a form inside a closed Dialog is reachable only where
66
+ Invoker Commands are supported. `data-turbo-confirm` also requires Turbo. Use
67
+ the server-owned review route when confirmation must be unavoidable. See
68
+ [Browser support](../browser_support.md).
102
69
 
103
70
  ## Tests
104
71
 
105
- Request-test authorization, deletion, 303 redirect, and flash. System-test the dialog only when the reviewed flow matters: trigger opens it, Cancel closes and restores focus, and the destructive submit removes the record.
72
+ Request-test authorization, mutation, flash, and the `303` redirect. For a
73
+ reviewed flow, system-test open, cancel with focus restoration, and confirm.
@@ -1,57 +1,39 @@
1
1
  # Flash and toast
2
2
 
3
- Rails flash is the server-side feedback contract. Render it once in the application layout with Nitro's toast adapter so redirects, Turbo visits, and full-page fallbacks all use the same path.
3
+ **Audience:** Coding agents and developers presenting Rails server feedback.
4
4
 
5
5
  ## Summary
6
6
 
7
- - Render `NitroKit::Toast::FlashMessages` once in the application layout; Rails
8
- flash stays the single server-side feedback contract.
9
- - `notice` maps to the default presentation, `alert` and `error` to error, and
10
- `success`, `warning`, and `info` to their matching variants.
11
- - Every toast item is Turbo-temporary so a cached page never replays old
12
- feedback, while the region itself survives and stays addressable.
13
- - Use `flash.now` when rendering the current request; a Turbo Stream that does
14
- not redirect updates the same stable notification region.
15
- - Do not introduce a client-side notification store for server outcomes.
7
+ - Rails flash is the server-side feedback contract; render
8
+ `NitroKit::Toast::FlashMessages` once in the application layout.
9
+ - Use redirect flash for navigation and `flash.now` when rendering the current
10
+ request.
11
+ - Toast items are Turbo-temporary so cached pages do not replay them; the
12
+ region keeps a stable address for stream updates.
13
+ - Do not add a client-side notification store for server outcomes.
16
14
 
17
15
  ```ruby
18
- class UI::ApplicationLayout < Phlex::HTML
19
- include Phlex::Rails::Layout
20
- include Phlex::Rails::Helpers::Flash
21
-
22
- def view_template
23
- doctype
24
- html do
25
- head do
26
- stylesheet_link_tag("nitro_kit", data: { turbo_track: "reload" })
27
- end
28
- body do
29
- render NitroKit::Toast::FlashMessages.new(flash: flash)
30
- yield
31
- end
32
- end
33
- end
16
+ body do
17
+ render NitroKit::Toast::FlashMessages.new(flash: flash)
18
+ yield
34
19
  end
35
20
  ```
36
21
 
37
- Controllers set ordinary flash while redirecting:
22
+ Controllers use ordinary Rails flash:
38
23
 
39
24
  ```ruby
40
25
  redirect_to projects_path, status: :see_other, notice: "Project created"
41
- redirect_to billing_path, status: :see_other, alert: "Payment method was declined"
42
- ```
43
-
44
- `notice` maps to the default presentation, `alert` and `error` to error, and `success`, `warning`, and `info` to their matching variants. Every toast item is Turbo-temporary so a cached page does not replay old feedback, dismissible or not. The region itself survives, so `#{toast id}-list` stays addressable.
45
26
 
46
- Use `flash.now` only when rendering in the current request:
47
-
48
- ```ruby
49
27
  flash.now[:alert] = "Import failed"
50
28
  render UI::ImportForm.new(@import), status: :unprocessable_entity
51
29
  ```
52
30
 
53
- For a request-scoped Turbo Stream that does not redirect, update a stable notification region in the same stream response. Keep the HTML branch and flash fallback. Do not introduce a client-side notification store for server outcomes.
31
+ `notice` uses the default presentation; `alert` and `error` map to error;
32
+ `success`, `warning`, and `info` map to matching variants. A request-scoped
33
+ Turbo Stream may update the same stable Toast list, but must keep an HTML
34
+ branch and flash fallback.
54
35
 
55
36
  ## Tests
56
37
 
57
- Controller tests assert the flash severity and message. One layout or integration test should prove the flash renders through `section[data-nk=toast]`. Nitro's own tests cover timers and dismissal behavior.
38
+ Assert flash severity and message in request tests. One layout or integration
39
+ test should prove rendering through `section[data-nk=toast]`.
@@ -1,59 +1,36 @@
1
1
  # Inline edit
2
2
 
3
- Wrap one complete resource region in a stable Turbo Frame. Show, edit, validation failure, success, and Cancel all return that same frame ID.
3
+ **Audience:** Coding agents and developers editing one resource region in
4
+ place with Turbo Frames.
4
5
 
5
6
  ## Summary
6
7
 
7
- - One complete resource region lives in a stable Turbo Frame; show, edit,
8
- invalid, success, and Cancel all return that same frame ID.
9
- - Invalid updates render the editing frame with 422; success redirects with 303
10
- and Turbo extracts the matching read-only frame.
11
- - The show response must contain the same `turbo_frame_tag(dom_id(record))` as
12
- the edit response.
13
- - Use a Turbo Stream only when the update also changes another region, such as
14
- a page title or a summary count.
8
+ - Wrap the complete resource region in one stable Turbo Frame.
9
+ - Show, edit, validation failure, success, and Cancel return the same frame ID.
10
+ - Invalid updates render with `422`; successful updates redirect with `303`.
11
+ - Use a Turbo Stream only when the update changes another region too.
15
12
 
16
13
  ```ruby
17
- module UI
18
- class ProjectPanel < Phlex::HTML
19
- include Phlex::Rails::Helpers::DOMID
20
- include Phlex::Rails::Helpers::TurboFrameTag
21
-
22
- def initialize(project, editing: false)
23
- @project = project
24
- @editing = editing
25
- end
26
-
27
- def view_template
28
- turbo_frame_tag(dom_id(project)) do
29
- if editing
30
- render UI::ProjectFormFields.new(project)
31
- else
32
- render NitroKit::Card.new do |card|
33
- card.title(project.name)
34
- card.body { project.description }
35
- card.footer do
36
- render NitroKit::Button.new("Edit", href: "/projects/#{project.id}/edit")
37
- end
38
- end
39
- end
40
- end
14
+ turbo_frame_tag(dom_id(project)) do
15
+ if editing
16
+ render UI::ProjectForm.new(project)
17
+ else
18
+ div do
19
+ h2 { project.name }
20
+ render NitroKit::Button.new("Edit", href: edit_project_path(project))
41
21
  end
42
-
43
- private
44
- attr_reader :project, :editing
45
22
  end
46
23
  end
47
24
  ```
48
25
 
49
- The edit form submits normally within the frame. Its Cancel link targets the resource show action, which returns the read-only frame. Invalid updates render the editing frame with 422. A successful update redirects with 303 to the resource action; Turbo follows the redirect and extracts the matching read-only frame:
50
-
51
- ```ruby
52
- redirect_to @project, status: :see_other, notice: "Project updated"
53
- ```
54
-
55
- The show response must contain `turbo_frame_tag(dom_id(project))` just like the edit response. If success renders directly instead, return frame-shaped HTML rather than labeling a bare frame as a Turbo Stream. Use a stream only when the update also changes another region, such as a page title or summary count.
26
+ The edit form submits inside the frame. Cancel links to the show action, whose
27
+ response contains the same `turbo_frame_tag(dom_id(project))`. Invalid updates
28
+ render the editing frame with `status: :unprocessable_entity`. Success redirects
29
+ with `status: :see_other`; Turbo follows it and extracts the matching read-only
30
+ frame.
56
31
 
57
32
  ## Tests
58
33
 
59
- Assert every endpoint returns the same `turbo-frame` ID. Cover invalid values remaining visible at 422 and Cancel restoring the read-only view. Add a system test when focus or scroll preservation is important.
34
+ Assert the same frame ID on every endpoint, submitted values and errors at
35
+ `422`, success at `303`, and Cancel restoring the read-only view. Add a system
36
+ test only when focus, scroll, or multi-region behavior matters.