nitro_kit 2.0.0.alpha.3 → 2.0.0.alpha.5
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 +194 -0
- data/README.md +34 -23
- data/STYLE_GUIDE.md +10 -0
- data/app/assets/stylesheets/nitro_kit-tailwind-v4.css +3 -3
- data/app/assets/stylesheets/nitro_kit.css +1220 -572
- data/app/components/nitro_kit/accordion.rb +1 -19
- data/app/components/nitro_kit/alert.rb +1 -11
- data/app/components/nitro_kit/app_navigation.rb +44 -9
- data/app/components/nitro_kit/appearance_picker.rb +10 -1
- data/app/components/nitro_kit/badge.rb +13 -4
- data/app/components/nitro_kit/combobox.rb +1 -17
- data/app/components/nitro_kit/command_palette.rb +1 -0
- data/app/components/nitro_kit/dropzone.rb +1 -1
- data/app/components/nitro_kit/form_builder.rb +1 -1
- data/app/components/nitro_kit/select.rb +1 -14
- data/app/components/nitro_kit/tabs.rb +1 -0
- data/app/javascript/controllers/nk/command_palette_controller.js +4 -0
- data/app/javascript/controllers/nk/tabs_controller.js +13 -1
- data/docs/agent_guide.md +85 -138
- data/docs/agent_native_spec.md +72 -400
- data/docs/browser_support.md +77 -188
- data/docs/component_contracts.md +56 -36
- data/docs/customization.md +675 -89
- data/docs/hotwire.md +61 -99
- data/docs/initialization_prompt.md +33 -57
- data/docs/migration_1_to_2.md +34 -18
- data/docs/patterns/application_foundation.md +45 -121
- data/docs/patterns/crud_resource.md +44 -129
- data/docs/patterns/destructive_action.md +45 -102
- 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 -78
- data/docs/rails_integration.md +96 -474
- data/lib/nitro_kit/installation.rb +19 -3
- data/lib/nitro_kit/migration_inventory.rb +15 -8
- data/lib/nitro_kit/version.rb +1 -1
- data/plugins/nitro-kit/.codex-plugin/plugin.json +4 -4
- data/plugins/nitro-kit/skills/nitro-kit-hotwire/SKILL.md +12 -0
- data/plugins/nitro-kit/skills/nitro-kit-hotwire/agents/openai.yaml +1 -1
- data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +22 -0
- data/plugins/nitro-kit/skills/nitro-kit-rails/agents/openai.yaml +1 -1
- data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +33 -11
- data/plugins/nitro-kit/skills/nitro-kit-ui/agents/openai.yaml +1 -1
- data/src/stylesheets/nitro_kit/components/accordion.css +4 -4
- data/src/stylesheets/nitro_kit/components/alert.css +20 -29
- data/src/stylesheets/nitro_kit/components/app_navigation.css +74 -2
- data/src/stylesheets/nitro_kit/components/app_shell.css +3 -3
- data/src/stylesheets/nitro_kit/components/appearance_picker.css +20 -8
- data/src/stylesheets/nitro_kit/components/auth_shell.css +2 -2
- data/src/stylesheets/nitro_kit/components/avatar.css +12 -8
- data/src/stylesheets/nitro_kit/components/avatar_stack.css +12 -10
- data/src/stylesheets/nitro_kit/components/badge.css +13 -9
- data/src/stylesheets/nitro_kit/components/button.css +42 -25
- data/src/stylesheets/nitro_kit/components/card.css +2 -2
- data/src/stylesheets/nitro_kit/components/checkbox.css +29 -46
- data/src/stylesheets/nitro_kit/components/checkbox_group.css +3 -3
- data/src/stylesheets/nitro_kit/components/combobox.css +6 -6
- data/src/stylesheets/nitro_kit/components/command_palette.css +62 -4
- data/src/stylesheets/nitro_kit/components/container.css +6 -6
- data/src/stylesheets/nitro_kit/components/control_group.css +9 -9
- data/src/stylesheets/nitro_kit/components/danger_zone.css +6 -6
- data/src/stylesheets/nitro_kit/components/data_section.css +4 -4
- data/src/stylesheets/nitro_kit/components/details_table.css +7 -5
- data/src/stylesheets/nitro_kit/components/dialog.css +3 -3
- data/src/stylesheets/nitro_kit/components/dropdown.css +7 -7
- data/src/stylesheets/nitro_kit/components/dropzone.css +7 -7
- data/src/stylesheets/nitro_kit/components/empty_state.css +4 -4
- data/src/stylesheets/nitro_kit/components/field.css +2 -2
- data/src/stylesheets/nitro_kit/components/fieldset.css +2 -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 +33 -6
- data/src/stylesheets/nitro_kit/components/page_header.css +4 -4
- data/src/stylesheets/nitro_kit/components/pagination.css +3 -3
- data/src/stylesheets/nitro_kit/components/pagination_bar.css +2 -2
- data/src/stylesheets/nitro_kit/components/palette.css +140 -72
- data/src/stylesheets/nitro_kit/components/progressive_image.css +1 -1
- data/src/stylesheets/nitro_kit/components/radio_button.css +28 -33
- data/src/stylesheets/nitro_kit/components/radio_button_group.css +6 -6
- data/src/stylesheets/nitro_kit/components/rich_text_area.css +2 -2
- data/src/stylesheets/nitro_kit/components/select.css +24 -8
- data/src/stylesheets/nitro_kit/components/settings_layout.css +5 -5
- data/src/stylesheets/nitro_kit/components/settings_section.css +2 -2
- data/src/stylesheets/nitro_kit/components/sheet.css +10 -10
- data/src/stylesheets/nitro_kit/components/stat_grid.css +1 -1
- data/src/stylesheets/nitro_kit/components/switch.css +34 -40
- data/src/stylesheets/nitro_kit/components/table.css +8 -4
- data/src/stylesheets/nitro_kit/components/tabs.css +5 -1
- data/src/stylesheets/nitro_kit/components/textarea.css +20 -5
- data/src/stylesheets/nitro_kit/components/toast.css +11 -58
- data/src/stylesheets/nitro_kit/components/toolbar.css +2 -2
- data/src/stylesheets/nitro_kit/components/tooltip.css +4 -0
- data/src/stylesheets/nitro_kit/components/typeset.css +4 -4
- data/src/stylesheets/nitro_kit/reset.css +6 -6
- data/src/stylesheets/nitro_kit/tokens.css +497 -74
- metadata +1 -2
- data/docs/new_app_strategy.md +0 -22
|
@@ -106,25 +106,7 @@ module NitroKit
|
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
def chevron
|
|
109
|
-
|
|
110
|
-
**slot_attributes(
|
|
111
|
-
:icon,
|
|
112
|
-
attributes: {
|
|
113
|
-
viewbox: "0 0 16 16",
|
|
114
|
-
width: 16,
|
|
115
|
-
height: 16,
|
|
116
|
-
fill: "none",
|
|
117
|
-
stroke: "currentColor",
|
|
118
|
-
stroke_width: 1.5,
|
|
119
|
-
stroke_linecap: "round",
|
|
120
|
-
stroke_linejoin: "round",
|
|
121
|
-
focusable: "false",
|
|
122
|
-
aria: { hidden: true }
|
|
123
|
-
}
|
|
124
|
-
)
|
|
125
|
-
) do |svg|
|
|
126
|
-
svg.path(d: "m4 6 4 4 4-4")
|
|
127
|
-
end
|
|
109
|
+
render_in_slot(Icon.new(:chevron_down, size: :sm), :icon)
|
|
128
110
|
end
|
|
129
111
|
|
|
130
112
|
def trigger_id(item)
|
|
@@ -2,17 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module NitroKit
|
|
4
4
|
class Alert < Component
|
|
5
|
-
VARIANTS = %i[default info success warning
|
|
6
|
-
|
|
7
|
-
# The palette family each semantic variant borrows in `palette.css`.
|
|
8
|
-
# Alert owns this mapping; it is not the Badge color axis.
|
|
9
|
-
VARIANT_PALETTE = {
|
|
10
|
-
default: :zinc,
|
|
11
|
-
info: :blue,
|
|
12
|
-
success: :green,
|
|
13
|
-
warning: :amber,
|
|
14
|
-
error: :red
|
|
15
|
-
}.freeze
|
|
5
|
+
VARIANTS = %i[default info success warning destructive].freeze
|
|
16
6
|
|
|
17
7
|
LIVE_MODES = %i[off polite assertive].freeze
|
|
18
8
|
|
|
@@ -5,8 +5,8 @@ module NitroKit
|
|
|
5
5
|
alias_method :html_header, :header
|
|
6
6
|
alias_method :html_footer, :footer
|
|
7
7
|
|
|
8
|
-
Item = ::Data.define(:text, :href, :icon, :badge, :current, :html, :aria, :data, :css_class)
|
|
9
|
-
Section = ::Data.define(:label, :entries)
|
|
8
|
+
Item = ::Data.define(:text, :href, :icon, :icon_end, :badge, :current, :html, :aria, :data, :css_class)
|
|
9
|
+
Section = ::Data.define(:label, :entries, :collapsible, :expanded)
|
|
10
10
|
Entry = ::Data.define(:kind)
|
|
11
11
|
|
|
12
12
|
def initialize(
|
|
@@ -78,10 +78,16 @@ module NitroKit
|
|
|
78
78
|
nil
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
def section(label: nil, &content)
|
|
81
|
+
def section(label: nil, collapsible: false, expanded: true, &content)
|
|
82
82
|
ensure_phase!(:body, :section)
|
|
83
83
|
raise ArgumentError, "AppNavigation section requires a block" unless content
|
|
84
84
|
|
|
85
|
+
collapsible = validate_boolean!(:collapsible, collapsible)
|
|
86
|
+
expanded = validate_boolean!(:expanded, expanded)
|
|
87
|
+
if collapsible && label.nil?
|
|
88
|
+
raise ArgumentError, "AppNavigation collapsible section requires a label"
|
|
89
|
+
end
|
|
90
|
+
|
|
85
91
|
entries = []
|
|
86
92
|
previous_entries = @entry_target
|
|
87
93
|
previous_phase = @phase
|
|
@@ -94,7 +100,12 @@ module NitroKit
|
|
|
94
100
|
unless entries.any?(Item)
|
|
95
101
|
raise ArgumentError, "AppNavigation section requires at least one item"
|
|
96
102
|
end
|
|
97
|
-
previous_entries << Section.new(
|
|
103
|
+
previous_entries << Section.new(
|
|
104
|
+
label: validate_optional_text!(:label, label),
|
|
105
|
+
entries:,
|
|
106
|
+
collapsible:,
|
|
107
|
+
expanded:
|
|
108
|
+
)
|
|
98
109
|
nil
|
|
99
110
|
ensure
|
|
100
111
|
@entry_target = previous_entries
|
|
@@ -106,6 +117,7 @@ module NitroKit
|
|
|
106
117
|
text,
|
|
107
118
|
href:,
|
|
108
119
|
icon: nil,
|
|
120
|
+
icon_end: nil,
|
|
109
121
|
badge: nil,
|
|
110
122
|
badge_color: :neutral,
|
|
111
123
|
current: false,
|
|
@@ -118,6 +130,7 @@ module NitroKit
|
|
|
118
130
|
text = validate_text!(:text, text)
|
|
119
131
|
href = validate_text!(:href, href)
|
|
120
132
|
icon = item_icon(icon)
|
|
133
|
+
icon_end = item_icon(icon_end, name: :icon_end)
|
|
121
134
|
badge = item_badge(badge, badge_color)
|
|
122
135
|
current = validate_boolean!(:current, current)
|
|
123
136
|
|
|
@@ -130,6 +143,7 @@ module NitroKit
|
|
|
130
143
|
text:,
|
|
131
144
|
href:,
|
|
132
145
|
icon:,
|
|
146
|
+
icon_end:,
|
|
133
147
|
badge:,
|
|
134
148
|
current:,
|
|
135
149
|
html:,
|
|
@@ -205,13 +219,33 @@ module NitroKit
|
|
|
205
219
|
|
|
206
220
|
def render_section(entry)
|
|
207
221
|
li(**slot_attributes(:section)) do
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
222
|
+
if entry.collapsible
|
|
223
|
+
details(
|
|
224
|
+
**slot_attributes(:section_disclosure, attributes: { open: entry.expanded })
|
|
225
|
+
) do
|
|
226
|
+
summary(**slot_attributes(:section_label)) do
|
|
227
|
+
plain(entry.label)
|
|
228
|
+
section_chevron
|
|
229
|
+
end
|
|
230
|
+
render_section_list(entry)
|
|
231
|
+
end
|
|
232
|
+
else
|
|
233
|
+
span(**slot_attributes(:section_label)) { plain(entry.label) } if entry.label
|
|
234
|
+
render_section_list(entry)
|
|
211
235
|
end
|
|
212
236
|
end
|
|
213
237
|
end
|
|
214
238
|
|
|
239
|
+
def render_section_list(entry)
|
|
240
|
+
ul(**slot_attributes(:section_list, attributes: section_list_attributes(entry))) do
|
|
241
|
+
entry.entries.each { |child| render_entry(child) }
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def section_chevron
|
|
246
|
+
render_in_slot(Icon.new(:chevron_down, size: :xs), :section_icon)
|
|
247
|
+
end
|
|
248
|
+
|
|
215
249
|
def section_list_attributes(entry)
|
|
216
250
|
entry.label ? { aria: { label: entry.label } } : {}
|
|
217
251
|
end
|
|
@@ -234,6 +268,7 @@ module NitroKit
|
|
|
234
268
|
render_in_slot(entry.icon, :item_icon) if entry.icon
|
|
235
269
|
span(**slot_attributes(:item_label)) { plain(entry.text) }
|
|
236
270
|
render_in_slot(entry.badge, :item_badge) if entry.badge
|
|
271
|
+
render_in_slot(entry.icon_end, :item_icon_end) if entry.icon_end
|
|
237
272
|
end
|
|
238
273
|
end
|
|
239
274
|
end
|
|
@@ -277,10 +312,10 @@ module NitroKit
|
|
|
277
312
|
validate_text!(name, value)
|
|
278
313
|
end
|
|
279
314
|
|
|
280
|
-
def item_icon(value)
|
|
315
|
+
def item_icon(value, name: :icon)
|
|
281
316
|
return if value.nil?
|
|
282
317
|
unless (value.is_a?(String) || value.is_a?(Symbol)) && !value.to_s.strip.empty?
|
|
283
|
-
raise ArgumentError, "
|
|
318
|
+
raise ArgumentError, "#{name} must be a non-blank String or Symbol"
|
|
284
319
|
end
|
|
285
320
|
|
|
286
321
|
Icon.new(value, size: :sm)
|
|
@@ -13,6 +13,7 @@ module NitroKit
|
|
|
13
13
|
def initialize(
|
|
14
14
|
id:,
|
|
15
15
|
label: I18n.t("nitro_kit.appearance_picker.label"),
|
|
16
|
+
label_visible: true,
|
|
16
17
|
presentation: :segmented,
|
|
17
18
|
preference: :system,
|
|
18
19
|
html: {},
|
|
@@ -22,13 +23,21 @@ module NitroKit
|
|
|
22
23
|
)
|
|
23
24
|
@identifier = validate_id!("AppearancePicker id", id)
|
|
24
25
|
@label = validate_label!(label)
|
|
26
|
+
@label_visible = validate_boolean!(:label_visible, label_visible)
|
|
25
27
|
@presentation = validate_choice!(:presentation, presentation, PRESENTATIONS)
|
|
26
28
|
@preference = validate_choice!(:preference, preference, PREFERENCES)
|
|
29
|
+
if !@label_visible && @presentation != :segmented
|
|
30
|
+
raise ArgumentError,
|
|
31
|
+
"AppearancePicker label_visible: false requires the segmented presentation"
|
|
32
|
+
end
|
|
27
33
|
|
|
28
34
|
super(
|
|
29
35
|
component: :appearance_picker,
|
|
30
36
|
attributes: {
|
|
31
37
|
id: @identifier,
|
|
38
|
+
# Browsers give legends special layout, so a hidden label omits the
|
|
39
|
+
# element entirely and the fieldset keeps its name through ARIA.
|
|
40
|
+
aria: @label_visible ? {} : { label: @label },
|
|
32
41
|
data: {
|
|
33
42
|
controller: "nk--appearance",
|
|
34
43
|
presentation: @presentation,
|
|
@@ -50,7 +59,7 @@ module NitroKit
|
|
|
50
59
|
return render_dropdown if @presentation == :dropdown
|
|
51
60
|
|
|
52
61
|
fieldset(**root_attributes) do
|
|
53
|
-
legend(**slot_attributes(:legend)) { plain(@label) }
|
|
62
|
+
legend(**slot_attributes(:legend)) { plain(@label) } if @label_visible
|
|
54
63
|
div(**slot_attributes(:options)) do
|
|
55
64
|
PREFERENCES.each { |preference| render_option(preference) }
|
|
56
65
|
end
|
|
@@ -4,16 +4,25 @@ module NitroKit
|
|
|
4
4
|
class Badge < Component
|
|
5
5
|
VARIANTS = %i[default outline].freeze
|
|
6
6
|
SIZES = %i[xs sm md].freeze
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
# Two axes on one option, both public and both themeable.
|
|
8
|
+
#
|
|
9
|
+
# The semantic families follow the `--nk-palette-{family}` tint roles, so
|
|
10
|
+
# they move with an application's brand: retheme `--nk-palette-destructive` and
|
|
11
|
+
# every `destructive` badge follows. The decorative hues follow the
|
|
12
|
+
# `--nk-palette-{hue}` roles and stay the color they name, for categorical
|
|
13
|
+
# labelling where meaning is not the point.
|
|
14
|
+
SEMANTIC_COLORS = %i[neutral info success warning destructive].freeze
|
|
15
|
+
PALETTE_COLORS = %i[
|
|
16
|
+
red orange amber yellow lime green emerald teal cyan sky blue indigo
|
|
17
|
+
violet purple fuchsia pink rose
|
|
10
18
|
].freeze
|
|
19
|
+
COLORS = (PALETTE_COLORS + SEMANTIC_COLORS).freeze
|
|
11
20
|
|
|
12
21
|
def initialize(
|
|
13
22
|
text = nil,
|
|
14
23
|
variant: :default,
|
|
15
24
|
size: :md,
|
|
16
|
-
color: :
|
|
25
|
+
color: :neutral,
|
|
17
26
|
id: nil,
|
|
18
27
|
html: {},
|
|
19
28
|
aria: {},
|
|
@@ -117,23 +117,7 @@ module NitroKit
|
|
|
117
117
|
:input
|
|
118
118
|
)
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
**slot_attributes(
|
|
122
|
-
:icon,
|
|
123
|
-
attributes: {
|
|
124
|
-
viewbox: "0 0 16 16",
|
|
125
|
-
width: 16,
|
|
126
|
-
height: 16,
|
|
127
|
-
fill: "none",
|
|
128
|
-
stroke: "currentColor",
|
|
129
|
-
stroke_width: 1.5,
|
|
130
|
-
stroke_linecap: "round",
|
|
131
|
-
stroke_linejoin: "round",
|
|
132
|
-
focusable: "false",
|
|
133
|
-
aria: { hidden: true }
|
|
134
|
-
}
|
|
135
|
-
)
|
|
136
|
-
) { |svg| svg.path(d: "m4 6 4 4 4-4") }
|
|
120
|
+
render_in_slot(Icon.new(:chevron_down, size: :sm), :icon)
|
|
137
121
|
end
|
|
138
122
|
end
|
|
139
123
|
|
|
@@ -77,20 +77,7 @@ module NitroKit
|
|
|
77
77
|
def render_toggle_icon
|
|
78
78
|
return if @multiple
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
**slot_attributes(
|
|
82
|
-
:icon,
|
|
83
|
-
attributes: {
|
|
84
|
-
viewbox: "0 0 20 20",
|
|
85
|
-
fill: "none",
|
|
86
|
-
stroke: "currentColor",
|
|
87
|
-
stroke_width: 1.5,
|
|
88
|
-
aria: { hidden: true }
|
|
89
|
-
}
|
|
90
|
-
)
|
|
91
|
-
) do |icon|
|
|
92
|
-
icon.path(d: "m6.5 8 3.5 3.5L13.5 8", stroke_linecap: "round", stroke_linejoin: "round")
|
|
93
|
-
end
|
|
80
|
+
render_in_slot(Icon.new(:chevron_down, size: :sm), :icon)
|
|
94
81
|
end
|
|
95
82
|
|
|
96
83
|
def control_attributes
|
|
@@ -68,6 +68,10 @@ export default class extends Controller {
|
|
|
68
68
|
this.openTimer = setTimeout(() => this.#prepareOpenPanel(), 0);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
guardOpen(event) {
|
|
72
|
+
if (event.command === "show-modal" && !this.canOpen) event.preventDefault();
|
|
73
|
+
}
|
|
74
|
+
|
|
71
75
|
shortcut(event) {
|
|
72
76
|
if (
|
|
73
77
|
event.defaultPrevented ||
|
|
@@ -42,6 +42,14 @@ export default class extends Controller {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
reveal(event) {
|
|
46
|
+
const tab = this.tabTargets.find(
|
|
47
|
+
(candidate) => candidate.dataset.key === event.currentTarget.dataset.key,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (tab && !tab.disabled) this.activeValue = tab.dataset.key;
|
|
51
|
+
}
|
|
52
|
+
|
|
45
53
|
navigate(event) {
|
|
46
54
|
const rightToLeft = getComputedStyle(this.element).direction === "rtl";
|
|
47
55
|
const directionKeys =
|
|
@@ -143,11 +151,15 @@ export default class extends Controller {
|
|
|
143
151
|
|
|
144
152
|
this.panelTargets.forEach((panel) => {
|
|
145
153
|
const active = panel.dataset.key === value;
|
|
154
|
+
const tab = this.tabTargets.find(
|
|
155
|
+
(candidate) => candidate.dataset.key === panel.dataset.key,
|
|
156
|
+
);
|
|
146
157
|
|
|
147
|
-
panel.hidden = !active;
|
|
148
158
|
if (active) {
|
|
159
|
+
panel.removeAttribute("hidden");
|
|
149
160
|
panel.setAttribute("aria-hidden", "false");
|
|
150
161
|
} else {
|
|
162
|
+
panel.setAttribute("hidden", tab?.disabled ? "" : "until-found");
|
|
151
163
|
panel.removeAttribute("aria-hidden");
|
|
152
164
|
}
|
|
153
165
|
panel.dataset.state = active ? "active" : "inactive";
|
data/docs/agent_guide.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Nitro Kit application guide for coding agents
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Audience:** Coding agents changing a Rails application that uses Nitro Kit.
|
|
4
|
+
Humans should start with the [README](../README.md) and
|
|
5
|
+
[Rails integration guide](rails_integration.md).
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Resolve the installed version
|
|
6
8
|
|
|
7
9
|
From the application root, run:
|
|
8
10
|
|
|
@@ -10,145 +12,90 @@ From the application root, run:
|
|
|
10
12
|
bundle show nitro_kit
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
Read this guide from that directory
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
nitro_kit:upgrade_smoke_tests`. The generator preserves existing files and
|
|
73
|
-
skips missing Rails Minitest or system-test infrastructure with setup guidance.
|
|
74
|
-
Keep the generated explanatory header in each test: these are host-integration
|
|
75
|
-
regression checks, not substitutes for application-specific acceptance tests.
|
|
76
|
-
Run supported generated tests before and after the application conversion,
|
|
77
|
-
while retaining application-specific acceptance coverage for every inventoried
|
|
78
|
-
flow. Customize each generated `prepare_nitro_kit_upgrade_smoke_test` method
|
|
79
|
-
with host sign-in and account-selection helpers when `ApplicationController`
|
|
80
|
-
requires them; do not disable the host callbacks.
|
|
81
|
-
|
|
82
|
-
In an existing application, replace a form control only when the installed
|
|
83
|
-
Nitro Kit catalog provides a genuine semantic and behavioral equivalent.
|
|
84
|
-
Preserve its parameter name, IDs, values, errors, accessibility, uploads, and
|
|
85
|
-
browser behavior. If no equivalent exists, keep or re-express the control as
|
|
86
|
-
application-owned Rails and semantic HTML, optionally inside a custom
|
|
87
|
-
`form.field` composition. Never downgrade an editor, autocomplete, date range,
|
|
88
|
-
upload, or other specialized input to the nearest generic Nitro control merely
|
|
89
|
-
for visual consistency.
|
|
90
|
-
|
|
91
|
-
Do not retain copied Nitro Kit 1.x source as the fallback. Remove the legacy
|
|
92
|
-
component and preserve the unsupported behavior in clearly application-owned
|
|
93
|
-
code. Report the missing equivalent as a Nitro Kit coverage gap.
|
|
94
|
-
|
|
95
|
-
At the end of a migration, search the whole application for
|
|
96
|
-
`desperately_need_a_class:` and review every result. Aim for zero rather than
|
|
97
|
-
translating old utility classes mechanically. Move application layout and
|
|
98
|
-
visual treatment to wrappers, use semantic component options or native
|
|
99
|
-
attributes, accept incidental Nitro defaults, and keep unmatched product UI
|
|
100
|
-
application-owned. Remove generic class forwarding from shared builders.
|
|
101
|
-
Retain an escape only for a named external integration that requires a class
|
|
102
|
-
hook, and record that reason in the migration review.
|
|
103
|
-
|
|
104
|
-
## Use one interaction grammar
|
|
105
|
-
|
|
106
|
-
Choose the smallest primitive that completes the interaction:
|
|
107
|
-
|
|
108
|
-
1. Ordinary Rails links and forms under Turbo Drive.
|
|
109
|
-
2. A Turbo Frame for one independently navigable or replaceable region.
|
|
110
|
-
3. A request-scoped Turbo Stream response when one action changes multiple regions.
|
|
111
|
-
4. A broadcast only when other sessions must receive the change.
|
|
112
|
-
5. Application Stimulus only for browser-only behavior the preceding layers cannot express.
|
|
113
|
-
|
|
114
|
-
Successful non-GET HTML submissions redirect with `303 See Other`. Invalid form submissions render the same invalid model with `422 Unprocessable Entity`. GET parameters remain the source of truth for queryable collections. Frame identifiers come from `dom_id` or one named constant shared by rendering, responses, and tests.
|
|
115
|
-
|
|
116
|
-
## Stay inside the public boundary
|
|
15
|
+
Read this guide from that directory. The installed
|
|
16
|
+
[component contracts](component_contracts.md) define the public API.
|
|
17
|
+
|
|
18
|
+
| Task | Read |
|
|
19
|
+
| -------------------------------------------------- | ------------------------------------------------------------ |
|
|
20
|
+
| Rails models, routes, CRUD, and tests | [Rails conventions](rails_conventions.md) |
|
|
21
|
+
| Authentication, teams, shell, and settings | [Application foundation](patterns/application_foundation.md) |
|
|
22
|
+
| Complete product CRUD | [CRUD resource](patterns/crud_resource.md) |
|
|
23
|
+
| Components and composition | [Component contracts](component_contracts.md) |
|
|
24
|
+
| Installation, assets, forms, and Rails integration | [Rails integration](rails_integration.md) |
|
|
25
|
+
| Turbo, Frames, Streams, morphs, or Stimulus | [Hotwire](hotwire.md) |
|
|
26
|
+
| Browser compatibility or fallback behavior | [Browser support](browser_support.md) |
|
|
27
|
+
| Theme tokens and application CSS | [Customization](customization.md) |
|
|
28
|
+
| Nitro Kit 1.x upgrade | [Migration guide](migration_1_to_2.md) |
|
|
29
|
+
| Query, sort, filter, or paginate | [Queryable collection](patterns/queryable_collection.md) |
|
|
30
|
+
| Create, update, and validation | [Resource form](patterns/resource_form.md) |
|
|
31
|
+
| Delete, revoke, archive, or confirm | [Destructive action](patterns/destructive_action.md) |
|
|
32
|
+
| Flash messages and notifications | [Flash and toast](patterns/flash_and_toast.md) |
|
|
33
|
+
| Edit and cancel inside a page | [Inline edit](patterns/inline_edit.md) |
|
|
34
|
+
|
|
35
|
+
Before changing a Nitro Kit 1.x application, read the migration guide. Do not
|
|
36
|
+
infer 2.x APIs from memory or old application code.
|
|
37
|
+
|
|
38
|
+
## Discover optional product patterns
|
|
39
|
+
|
|
40
|
+
The installed skills, documentation, component contracts, and source are the
|
|
41
|
+
complete required path for using Nitro Kit. A Nitro Kit catalog may also be
|
|
42
|
+
available through MCP or another tool, but catalog access is optional and must
|
|
43
|
+
never block the work.
|
|
44
|
+
|
|
45
|
+
For greenfield planning or broad product work:
|
|
46
|
+
|
|
47
|
+
1. Check whether Nitro Kit catalog or MCP tools are available.
|
|
48
|
+
2. When available, inventory the catalog, then search by product workflow rather
|
|
49
|
+
than component name.
|
|
50
|
+
3. Retrieve the relevant patterns before implementation and record which will be
|
|
51
|
+
used, adapted, or deferred.
|
|
52
|
+
4. When unavailable, continue with the installed guidance and report no catalog
|
|
53
|
+
coverage claims.
|
|
54
|
+
|
|
55
|
+
For a focused component or interaction change, search only when a catalog tool
|
|
56
|
+
is already available and the task could benefit from a higher-level pattern.
|
|
57
|
+
`nitro_kit:doctor` validates installation and runtime contracts; it is not a
|
|
58
|
+
product-completeness audit.
|
|
59
|
+
|
|
60
|
+
## Preserve the application's architecture
|
|
61
|
+
|
|
62
|
+
For a greenfield application, run:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
bin/rails generate phlex:install
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Use Phlex for the application layout, route views, and reusable UI. In an established
|
|
69
|
+
application, preserve its view architecture and introduce Phlex only at the
|
|
70
|
+
requested boundary. Do not perform an application-wide migration unless it is
|
|
71
|
+
explicitly authorized.
|
|
72
|
+
|
|
73
|
+
## Stay within the public API
|
|
117
74
|
|
|
118
75
|
- Include `NitroKit` once in the application's base Phlex component and use
|
|
119
|
-
|
|
120
|
-
- Use
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
Do not copy component source, add `nk_*` helpers, invent a general ERB bridge, mutate Nitro-owned Stimulus controllers, or pass `class:` and `style:`. The intentionally loud `desperately_need_a_class:` escape exists only for external integrations that require a class hook.
|
|
140
|
-
|
|
141
|
-
## Application `AGENTS.md`
|
|
142
|
-
|
|
143
|
-
Install the durable project instruction and local skills from the consuming
|
|
144
|
-
application root:
|
|
76
|
+
Kit methods such as `Button(...)` and `Card(...)`.
|
|
77
|
+
- Use `NitroKit::Button(...)` when inclusion is inappropriate and `.new` only
|
|
78
|
+
when another API requires a component object.
|
|
79
|
+
- Select `NitroKit::FormBuilder` explicitly with Rails `form_with`.
|
|
80
|
+
- Put stacked fields and actions in `form.group` or `FieldGroup`.
|
|
81
|
+
- Use documented component options, compound methods, native attributes, and
|
|
82
|
+
public `--nk-*` tokens.
|
|
83
|
+
- Keep product policy, records, routes, authorization, queries, DOM IDs, and
|
|
84
|
+
server responses in application code.
|
|
85
|
+
- Do not copy Nitro source, add `nk_*` helpers, mutate Nitro controllers, or
|
|
86
|
+
pass `class:` or `style:`. Use `desperately_need_a_class:` only for a named
|
|
87
|
+
external integration that requires a class hook.
|
|
88
|
+
|
|
89
|
+
Follow the canonical [browser support policy](browser_support.md) for
|
|
90
|
+
JavaScript and fallback guarantees.
|
|
91
|
+
|
|
92
|
+
## Install project-local guidance
|
|
93
|
+
|
|
94
|
+
From the consuming application, run:
|
|
145
95
|
|
|
146
96
|
```sh
|
|
147
97
|
bin/rails generate nitro_kit:install
|
|
148
98
|
```
|
|
149
99
|
|
|
150
|
-
The installer
|
|
151
|
-
|
|
152
|
-
supported agents. The skills deliberately resolve the installed gem first, so
|
|
153
|
-
upgrading the gem upgrades the instructions they use. Re-run the generator
|
|
154
|
-
after an upgrade; it never copies component or controller source.
|
|
100
|
+
The installer updates its managed `AGENTS.md` block and local Nitro Kit skills
|
|
101
|
+
without copying component source. Re-run it after upgrading the gem.
|