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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +229 -1
- data/README.md +25 -10
- data/STYLE_GUIDE.md +47 -5
- data/app/assets/stylesheets/nitro_kit-tailwind-v4.css +3 -3
- data/app/assets/stylesheets/nitro_kit.css +2820 -1816
- data/app/components/nitro_kit/accordion.rb +2 -26
- data/app/components/nitro_kit/alert.rb +1 -11
- data/app/components/nitro_kit/app_navigation.rb +45 -11
- data/app/components/nitro_kit/app_shell.rb +1 -7
- data/app/components/nitro_kit/appearance_picker.rb +11 -12
- data/app/components/nitro_kit/avatar.rb +6 -1
- data/app/components/nitro_kit/avatar_stack.rb +3 -0
- data/app/components/nitro_kit/badge.rb +13 -4
- data/app/components/nitro_kit/button_group.rb +2 -2
- data/app/components/nitro_kit/checkbox.rb +7 -4
- data/app/components/nitro_kit/checkbox_group.rb +0 -25
- data/app/components/nitro_kit/combobox.rb +4 -33
- data/app/components/nitro_kit/command_palette.rb +42 -59
- data/app/components/nitro_kit/component.rb +57 -5
- data/app/components/nitro_kit/danger_zone.rb +5 -1
- data/app/components/nitro_kit/data_section.rb +28 -4
- data/app/components/nitro_kit/details_table.rb +19 -8
- data/app/components/nitro_kit/dialog.rb +23 -19
- data/app/components/nitro_kit/dropdown.rb +1 -6
- data/app/components/nitro_kit/dropzone.rb +4 -15
- data/app/components/nitro_kit/empty_state.rb +21 -2
- data/app/components/nitro_kit/field.rb +35 -11
- data/app/components/nitro_kit/form_builder.rb +1 -2
- data/app/components/nitro_kit/progressive_image.rb +2 -2
- data/app/components/nitro_kit/radio_button.rb +7 -4
- data/app/components/nitro_kit/radio_button_group.rb +0 -25
- data/app/components/nitro_kit/select.rb +4 -17
- data/app/components/nitro_kit/settings_layout.rb +17 -4
- data/app/components/nitro_kit/{form_section.rb → settings_section.rb} +18 -11
- data/app/components/nitro_kit/sheet.rb +29 -20
- data/app/components/nitro_kit/stat_grid.rb +18 -2
- data/app/components/nitro_kit/table.rb +22 -3
- data/app/components/nitro_kit/tabs.rb +2 -13
- data/app/components/nitro_kit/toast.rb +4 -2
- data/app/components/nitro_kit/toolbar.rb +2 -0
- data/app/components/nitro_kit/tooltip.rb +7 -10
- data/app/javascript/controllers/nk/app_shell_controller.js +3 -3
- data/app/javascript/controllers/nk/appearance_controller.js +6 -0
- data/app/javascript/controllers/nk/combobox_controller.js +51 -1
- data/app/javascript/controllers/nk/command_palette_controller.js +25 -11
- data/app/javascript/controllers/nk/dialog_controller.js +54 -0
- data/app/javascript/controllers/nk/dropdown_controller.js +45 -0
- data/app/javascript/controllers/nk/dropzone_controller.js +2 -2
- data/app/javascript/controllers/nk/progressive_image_controller.js +11 -7
- data/app/javascript/controllers/nk/tabs_controller.js +21 -2
- data/app/javascript/controllers/nk/tooltip_controller.js +9 -0
- data/config/locales/en.yml +4 -1
- data/docs/agent_guide.md +63 -123
- data/docs/agent_native_spec.md +72 -383
- data/docs/browser_support.md +82 -0
- data/docs/component_contracts.md +108 -82
- data/docs/customization.md +689 -91
- data/docs/hotwire.md +50 -76
- data/docs/initialization_prompt.md +26 -31
- data/docs/migration_1_to_2.md +88 -30
- data/docs/patterns/application_foundation.md +45 -119
- data/docs/patterns/crud_resource.md +44 -126
- data/docs/patterns/destructive_action.md +45 -77
- data/docs/patterns/flash_and_toast.md +18 -36
- data/docs/patterns/inline_edit.md +21 -44
- data/docs/patterns/queryable_collection.md +65 -161
- data/docs/patterns/resource_form.md +33 -81
- data/docs/rails_conventions.md +38 -75
- data/docs/rails_integration.md +96 -446
- data/lib/generators/nitro_kit/upgrade_smoke_tests_generator.rb +12 -0
- data/lib/nitro_kit/installation.rb +20 -7
- data/lib/nitro_kit/migration_inventory.rb +275 -8
- data/lib/nitro_kit/upgrade_smoke_test.rb +10 -12
- data/lib/nitro_kit/version.rb +1 -1
- data/lib/rails/commands/nitro_kit/nitro_kit_command.rb +10 -3
- data/plugins/nitro-kit/skills/nitro-kit-hotwire/SKILL.md +4 -0
- data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +13 -1
- data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +32 -15
- data/src/stylesheets/nitro_kit/components/accordion.css +44 -44
- data/src/stylesheets/nitro_kit/components/alert.css +27 -36
- data/src/stylesheets/nitro_kit/components/app_navigation.css +92 -20
- data/src/stylesheets/nitro_kit/components/app_shell.css +199 -196
- data/src/stylesheets/nitro_kit/components/appearance_picker.css +61 -39
- data/src/stylesheets/nitro_kit/components/auth_shell.css +2 -2
- data/src/stylesheets/nitro_kit/components/avatar.css +15 -11
- data/src/stylesheets/nitro_kit/components/avatar_stack.css +48 -29
- data/src/stylesheets/nitro_kit/components/badge.css +13 -9
- data/src/stylesheets/nitro_kit/components/button.css +121 -79
- data/src/stylesheets/nitro_kit/components/button_group.css +19 -14
- data/src/stylesheets/nitro_kit/components/card.css +12 -6
- data/src/stylesheets/nitro_kit/components/checkbox.css +54 -67
- data/src/stylesheets/nitro_kit/components/checkbox_group.css +16 -16
- data/src/stylesheets/nitro_kit/components/combobox.css +39 -29
- data/src/stylesheets/nitro_kit/components/command_palette.css +110 -51
- data/src/stylesheets/nitro_kit/components/container.css +6 -6
- data/src/stylesheets/nitro_kit/components/control_group.css +61 -67
- data/src/stylesheets/nitro_kit/components/danger_zone.css +8 -8
- data/src/stylesheets/nitro_kit/components/data_section.css +4 -4
- data/src/stylesheets/nitro_kit/components/details_table.css +17 -15
- data/src/stylesheets/nitro_kit/components/dialog.css +8 -5
- data/src/stylesheets/nitro_kit/components/dropdown.css +36 -30
- data/src/stylesheets/nitro_kit/components/dropzone.css +78 -35
- data/src/stylesheets/nitro_kit/components/empty_state.css +7 -7
- data/src/stylesheets/nitro_kit/components/field.css +32 -28
- data/src/stylesheets/nitro_kit/components/field_group.css +13 -0
- data/src/stylesheets/nitro_kit/components/fieldset.css +8 -2
- data/src/stylesheets/nitro_kit/components/flex.css +1 -1
- data/src/stylesheets/nitro_kit/components/grid.css +1 -1
- data/src/stylesheets/nitro_kit/components/icon.css +5 -5
- data/src/stylesheets/nitro_kit/components/input.css +42 -8
- data/src/stylesheets/nitro_kit/components/layout.css +165 -165
- data/src/stylesheets/nitro_kit/components/page_header.css +7 -7
- data/src/stylesheets/nitro_kit/components/pagination.css +58 -49
- data/src/stylesheets/nitro_kit/components/pagination_bar.css +9 -9
- data/src/stylesheets/nitro_kit/components/palette.css +145 -77
- data/src/stylesheets/nitro_kit/components/progressive_image.css +45 -55
- data/src/stylesheets/nitro_kit/components/radio_button.css +48 -46
- data/src/stylesheets/nitro_kit/components/radio_button_group.css +49 -49
- data/src/stylesheets/nitro_kit/components/rich_text_area.css +16 -1
- data/src/stylesheets/nitro_kit/components/select.css +24 -15
- data/src/stylesheets/nitro_kit/components/settings_layout.css +26 -23
- data/src/stylesheets/nitro_kit/components/settings_section.css +84 -0
- data/src/stylesheets/nitro_kit/components/sheet.css +30 -10
- data/src/stylesheets/nitro_kit/components/stat_grid.css +11 -10
- data/src/stylesheets/nitro_kit/components/switch.css +60 -62
- data/src/stylesheets/nitro_kit/components/table.css +43 -35
- data/src/stylesheets/nitro_kit/components/tabs.css +48 -40
- data/src/stylesheets/nitro_kit/components/textarea.css +20 -5
- data/src/stylesheets/nitro_kit/components/toast.css +11 -47
- data/src/stylesheets/nitro_kit/components/toolbar.css +16 -41
- data/src/stylesheets/nitro_kit/components/tooltip.css +38 -29
- data/src/stylesheets/nitro_kit/components/typeset.css +150 -4
- data/src/stylesheets/nitro_kit/reset.css +15 -15
- data/src/stylesheets/nitro_kit/tokens.css +501 -79
- metadata +4 -4
- data/docs/new_app_strategy.md +0 -22
- data/src/stylesheets/nitro_kit/components/form_section.css +0 -71
|
@@ -1,144 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Complete product resource
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
##
|
|
17
|
+
## Resource map
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
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
|
-
##
|
|
15
|
+
## Choose one confirmation path
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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,
|
|
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
|
-
|
|
3
|
+
**Audience:** Coding agents and developers presenting Rails server feedback.
|
|
4
4
|
|
|
5
5
|
## Summary
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3
|
+
**Audience:** Coding agents and developers editing one resource region in
|
|
4
|
+
place with Turbo Frames.
|
|
4
5
|
|
|
5
6
|
## Summary
|
|
6
7
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
- Invalid updates render
|
|
10
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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.
|