nitro_kit 2.0.0.alpha.3 → 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 +179 -0
- data/README.md +22 -26
- 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 +63 -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 +27 -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 +11 -3
- data/lib/nitro_kit/migration_inventory.rb +15 -8
- data/lib/nitro_kit/version.rb +1 -1
- data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +7 -0
- data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +15 -8
- 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,68 @@ 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
|
-
| Transient server feedback | `Toast::FlashMessages` |
|
|
51
|
-
| Rendered Markdown or rich text | `Container`, then `Typeset` |
|
|
52
|
-
| General grouping | `Card`, `Flex`, `Grid`, `Container` |
|
|
53
|
-
|
|
54
|
-
Application-specific product UI belongs under the application's namespace and composes Nitro components. Nitro owns component markup, styles, accessibility structure, and narrowly scoped progressive behavior. The application owns product policy, records, routes, authorization, queries, DOM IDs, and server responses.
|
|
55
|
-
|
|
56
|
-
For admin CRUD, treat the starter and entitled flows as complete compositions:
|
|
57
|
-
use toolbar Back links on child routes, keep deletion on edit, keep status in
|
|
58
|
-
the details flow, implement full invitation and membership management, and use
|
|
59
|
-
native links for settings destinations. Do not stop after rendering a screen
|
|
60
|
-
that only looks structurally similar.
|
|
61
|
-
|
|
62
|
-
## Preserve controls during migration
|
|
63
|
-
|
|
64
|
-
Before changing a 1.x application, read `docs/migration_1_to_2.md`. Inventory
|
|
65
|
-
user-facing flows and behavior before mapping helpers. When the Nitro Kit MCP
|
|
66
|
-
catalog is available, search by product workflow rather than old component
|
|
67
|
-
name, then prefer the highest-level matching composition before replacing
|
|
68
|
-
atoms.
|
|
69
|
-
|
|
70
|
-
After establishing that inventory, generate the host integration smoke tests
|
|
71
|
-
from the currently bundled gem with `bin/rails generate
|
|
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
|
+
## Preserve the application's architecture
|
|
39
|
+
|
|
40
|
+
For a greenfield application, run:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
bin/rails generate phlex:install
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use Phlex for the application layout, route views, and reusable UI. In an established
|
|
47
|
+
application, preserve its view architecture and introduce Phlex only at the
|
|
48
|
+
requested boundary. Do not perform an application-wide migration unless it is
|
|
49
|
+
explicitly authorized.
|
|
50
|
+
|
|
51
|
+
## Stay within the public API
|
|
117
52
|
|
|
118
53
|
- 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:
|
|
54
|
+
Kit methods such as `Button(...)` and `Card(...)`.
|
|
55
|
+
- Use `NitroKit::Button(...)` when inclusion is inappropriate and `.new` only
|
|
56
|
+
when another API requires a component object.
|
|
57
|
+
- Select `NitroKit::FormBuilder` explicitly with Rails `form_with`.
|
|
58
|
+
- Put stacked fields and actions in `form.group` or `FieldGroup`.
|
|
59
|
+
- Use documented component options, compound methods, native attributes, and
|
|
60
|
+
public `--nk-*` tokens.
|
|
61
|
+
- Keep product policy, records, routes, authorization, queries, DOM IDs, and
|
|
62
|
+
server responses in application code.
|
|
63
|
+
- Do not copy Nitro source, add `nk_*` helpers, mutate Nitro controllers, or
|
|
64
|
+
pass `class:` or `style:`. Use `desperately_need_a_class:` only for a named
|
|
65
|
+
external integration that requires a class hook.
|
|
66
|
+
|
|
67
|
+
Follow the canonical [browser support policy](browser_support.md) for
|
|
68
|
+
JavaScript and fallback guarantees.
|
|
69
|
+
|
|
70
|
+
## Install project-local guidance
|
|
71
|
+
|
|
72
|
+
From the consuming application, run:
|
|
145
73
|
|
|
146
74
|
```sh
|
|
147
75
|
bin/rails generate nitro_kit:install
|
|
148
76
|
```
|
|
149
77
|
|
|
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.
|
|
78
|
+
The installer updates its managed `AGENTS.md` block and local Nitro Kit skills
|
|
79
|
+
without copying component source. Re-run it after upgrading the gem.
|