nitro_kit 2.0.0.alpha.2 → 2.0.0.alpha.3

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 (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -1
  3. data/README.md +23 -4
  4. data/STYLE_GUIDE.md +37 -5
  5. data/app/assets/stylesheets/nitro_kit.css +1714 -1358
  6. data/app/components/nitro_kit/accordion.rb +1 -7
  7. data/app/components/nitro_kit/app_navigation.rb +1 -2
  8. data/app/components/nitro_kit/app_shell.rb +1 -7
  9. data/app/components/nitro_kit/appearance_picker.rb +1 -11
  10. data/app/components/nitro_kit/avatar.rb +6 -1
  11. data/app/components/nitro_kit/avatar_stack.rb +3 -0
  12. data/app/components/nitro_kit/button_group.rb +2 -2
  13. data/app/components/nitro_kit/checkbox.rb +7 -4
  14. data/app/components/nitro_kit/checkbox_group.rb +0 -25
  15. data/app/components/nitro_kit/combobox.rb +3 -16
  16. data/app/components/nitro_kit/command_palette.rb +41 -59
  17. data/app/components/nitro_kit/component.rb +57 -5
  18. data/app/components/nitro_kit/danger_zone.rb +5 -1
  19. data/app/components/nitro_kit/data_section.rb +28 -4
  20. data/app/components/nitro_kit/details_table.rb +19 -8
  21. data/app/components/nitro_kit/dialog.rb +23 -19
  22. data/app/components/nitro_kit/dropdown.rb +1 -6
  23. data/app/components/nitro_kit/dropzone.rb +3 -14
  24. data/app/components/nitro_kit/empty_state.rb +21 -2
  25. data/app/components/nitro_kit/field.rb +35 -11
  26. data/app/components/nitro_kit/form_builder.rb +0 -1
  27. data/app/components/nitro_kit/progressive_image.rb +2 -2
  28. data/app/components/nitro_kit/radio_button.rb +7 -4
  29. data/app/components/nitro_kit/radio_button_group.rb +0 -25
  30. data/app/components/nitro_kit/select.rb +3 -3
  31. data/app/components/nitro_kit/settings_layout.rb +17 -4
  32. data/app/components/nitro_kit/{form_section.rb → settings_section.rb} +18 -11
  33. data/app/components/nitro_kit/sheet.rb +29 -20
  34. data/app/components/nitro_kit/stat_grid.rb +18 -2
  35. data/app/components/nitro_kit/table.rb +22 -3
  36. data/app/components/nitro_kit/tabs.rb +1 -13
  37. data/app/components/nitro_kit/toast.rb +4 -2
  38. data/app/components/nitro_kit/toolbar.rb +2 -0
  39. data/app/components/nitro_kit/tooltip.rb +7 -10
  40. data/app/javascript/controllers/nk/app_shell_controller.js +3 -3
  41. data/app/javascript/controllers/nk/appearance_controller.js +6 -0
  42. data/app/javascript/controllers/nk/combobox_controller.js +51 -1
  43. data/app/javascript/controllers/nk/command_palette_controller.js +21 -11
  44. data/app/javascript/controllers/nk/dialog_controller.js +54 -0
  45. data/app/javascript/controllers/nk/dropdown_controller.js +45 -0
  46. data/app/javascript/controllers/nk/dropzone_controller.js +2 -2
  47. data/app/javascript/controllers/nk/progressive_image_controller.js +11 -7
  48. data/app/javascript/controllers/nk/tabs_controller.js +8 -1
  49. data/app/javascript/controllers/nk/tooltip_controller.js +9 -0
  50. data/config/locales/en.yml +4 -1
  51. data/docs/agent_guide.md +31 -16
  52. data/docs/agent_native_spec.md +27 -10
  53. data/docs/browser_support.md +193 -0
  54. data/docs/component_contracts.md +88 -82
  55. data/docs/customization.md +27 -15
  56. data/docs/hotwire.md +15 -3
  57. data/docs/initialization_prompt.md +31 -6
  58. data/docs/migration_1_to_2.md +61 -19
  59. data/docs/patterns/application_foundation.md +6 -4
  60. data/docs/patterns/crud_resource.md +6 -3
  61. data/docs/patterns/destructive_action.md +25 -0
  62. data/docs/patterns/resource_form.md +1 -1
  63. data/docs/rails_conventions.md +4 -1
  64. data/docs/rails_integration.md +39 -11
  65. data/lib/generators/nitro_kit/upgrade_smoke_tests_generator.rb +12 -0
  66. data/lib/nitro_kit/installation.rb +9 -4
  67. data/lib/nitro_kit/migration_inventory.rb +268 -8
  68. data/lib/nitro_kit/upgrade_smoke_test.rb +10 -12
  69. data/lib/nitro_kit/version.rb +1 -1
  70. data/lib/rails/commands/nitro_kit/nitro_kit_command.rb +10 -3
  71. data/plugins/nitro-kit/skills/nitro-kit-hotwire/SKILL.md +4 -0
  72. data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +6 -1
  73. data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +18 -8
  74. data/src/stylesheets/nitro_kit/components/accordion.css +40 -40
  75. data/src/stylesheets/nitro_kit/components/alert.css +9 -9
  76. data/src/stylesheets/nitro_kit/components/app_navigation.css +19 -19
  77. data/src/stylesheets/nitro_kit/components/app_shell.css +196 -193
  78. data/src/stylesheets/nitro_kit/components/appearance_picker.css +41 -31
  79. data/src/stylesheets/nitro_kit/components/avatar.css +3 -3
  80. data/src/stylesheets/nitro_kit/components/avatar_stack.css +36 -19
  81. data/src/stylesheets/nitro_kit/components/button.css +83 -58
  82. data/src/stylesheets/nitro_kit/components/button_group.css +19 -14
  83. data/src/stylesheets/nitro_kit/components/card.css +11 -5
  84. data/src/stylesheets/nitro_kit/components/checkbox.css +40 -36
  85. data/src/stylesheets/nitro_kit/components/checkbox_group.css +13 -13
  86. data/src/stylesheets/nitro_kit/components/combobox.css +33 -23
  87. data/src/stylesheets/nitro_kit/components/command_palette.css +48 -47
  88. data/src/stylesheets/nitro_kit/components/control_group.css +52 -58
  89. data/src/stylesheets/nitro_kit/components/danger_zone.css +3 -3
  90. data/src/stylesheets/nitro_kit/components/data_section.css +1 -1
  91. data/src/stylesheets/nitro_kit/components/details_table.css +13 -13
  92. data/src/stylesheets/nitro_kit/components/dialog.css +6 -3
  93. data/src/stylesheets/nitro_kit/components/dropdown.css +29 -23
  94. data/src/stylesheets/nitro_kit/components/dropzone.css +71 -28
  95. data/src/stylesheets/nitro_kit/components/empty_state.css +6 -6
  96. data/src/stylesheets/nitro_kit/components/field.css +30 -26
  97. data/src/stylesheets/nitro_kit/components/field_group.css +13 -0
  98. data/src/stylesheets/nitro_kit/components/fieldset.css +6 -0
  99. data/src/stylesheets/nitro_kit/components/input.css +9 -2
  100. data/src/stylesheets/nitro_kit/components/layout.css +165 -165
  101. data/src/stylesheets/nitro_kit/components/page_header.css +3 -3
  102. data/src/stylesheets/nitro_kit/components/pagination.css +55 -46
  103. data/src/stylesheets/nitro_kit/components/pagination_bar.css +7 -7
  104. data/src/stylesheets/nitro_kit/components/palette.css +20 -20
  105. data/src/stylesheets/nitro_kit/components/progressive_image.css +44 -54
  106. data/src/stylesheets/nitro_kit/components/radio_button.css +36 -29
  107. data/src/stylesheets/nitro_kit/components/radio_button_group.css +43 -43
  108. data/src/stylesheets/nitro_kit/components/rich_text_area.css +15 -0
  109. data/src/stylesheets/nitro_kit/components/select.css +2 -9
  110. data/src/stylesheets/nitro_kit/components/settings_layout.css +21 -18
  111. data/src/stylesheets/nitro_kit/components/settings_section.css +84 -0
  112. data/src/stylesheets/nitro_kit/components/sheet.css +23 -3
  113. data/src/stylesheets/nitro_kit/components/stat_grid.css +10 -9
  114. data/src/stylesheets/nitro_kit/components/switch.css +37 -33
  115. data/src/stylesheets/nitro_kit/components/table.css +35 -31
  116. data/src/stylesheets/nitro_kit/components/tabs.css +43 -39
  117. data/src/stylesheets/nitro_kit/components/toast.css +20 -9
  118. data/src/stylesheets/nitro_kit/components/toolbar.css +14 -39
  119. data/src/stylesheets/nitro_kit/components/tooltip.css +34 -29
  120. data/src/stylesheets/nitro_kit/components/typeset.css +147 -1
  121. data/src/stylesheets/nitro_kit/reset.css +9 -9
  122. data/src/stylesheets/nitro_kit/tokens.css +8 -9
  123. metadata +4 -3
  124. data/src/stylesheets/nitro_kit/components/form_section.css +0 -71
@@ -79,7 +79,15 @@ Color tokens need a light value, a no-JavaScript system fallback, and a dark val
79
79
 
80
80
  The media query matters when JavaScript or the appearance bootstrap is unavailable. When the runtime is active, `data-theme` is always the resolved `light` or `dark` appearance. `system` is a stored preference in `data-theme-preference`, not a third palette and never a `data-theme="system"` selector.
81
81
 
82
- Raised default Buttons have their own tokens, so their dark treatment can change without recoloring cards, dialogs, menus, or data-entry controls:
82
+ Buttons can also preserve a product-specific shape without changing inputs or surfaces:
83
+
84
+ ```css
85
+ :root {
86
+ --nk-button-radius: var(--nk-radius-full);
87
+ }
88
+ ```
89
+
90
+ Raised default Buttons have their own color tokens, so their dark treatment can change without recoloring cards, dialogs, menus, or data-entry controls:
83
91
 
84
92
  ```css
85
93
  @media (prefers-color-scheme: dark) {
@@ -187,7 +195,7 @@ Treat related tokens as a system:
187
195
  - Accent changes usually set `--nk-color-primary`, `--nk-color-primary-foreground`, and `--nk-color-focus` for both appearances. The default hover value is derived automatically; set `--nk-color-primary-hover` only when the derived color is unsuitable.
188
196
  - Neutral changes should coordinate canvas, surface, elevated, foreground, muted, border, and neutral-content pairs for both appearances.
189
197
  - Default Button changes use the `--nk-button-default-*` tokens. They are separate from `--nk-color-surface` so a raised neutral action can change without recoloring inputs, cards, dialogs, and menus.
190
- - Radius changes should move `--nk-radius-xs` through `--nk-radius-xl` together. Leave `--nk-radius-full` alone unless pills and circular controls should stop being fully rounded.
198
+ - Radius changes should move `--nk-radius-xs` through `--nk-radius-xl` together. Leave `--nk-radius-full` alone unless pills and circular controls should stop being fully rounded. Set `--nk-button-radius` when buttons intentionally use a distinct shape, such as a pill treatment, without changing inputs and surfaces.
191
199
  - Density changes should coordinate `--nk-space` with all five control-height tokens. Changing one component's internal gap is not a public theme contract.
192
200
  - Font changes normally set `--nk-font-sans`; set `--nk-font-mono`, text sizes, line heights, or weights only when the whole type system calls for it.
193
201
 
@@ -449,7 +457,10 @@ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
449
457
  eagerLoadControllersFrom("controllers", application);
450
458
  ```
451
459
 
452
- This registers Nitro's `controllers/nk/*` modules together with application controllers. Without importmap, Ruby and CSS still work, but a bundler-based application must expose and register those Stimulus modules itself. Nitro Kit ships no third-party JavaScript runtime, and Nitro Kit 2.0 has no JavaScript-package entrypoint.
460
+ This registers Nitro's `controllers/nk/*` modules together with application controllers. Without importmap, server-rendered HTML and CSS remain available, but controller-dependent behavior and compatibility bridges are unavailable unless a bundler-based application exposes and registers those Stimulus modules itself. Nitro Kit ships no third-party JavaScript runtime, and Nitro Kit 2.0 has no JavaScript-package entrypoint.
461
+
462
+ For the resulting behavior when those modules are not registered, use the
463
+ canonical no-JavaScript matrix in [`browser_support.md`](browser_support.md).
453
464
 
454
465
  ## Optional Tailwind CSS v4 adapter
455
466
 
@@ -471,7 +482,7 @@ Use `@theme inline` when a Tailwind theme variable references another custom pro
471
482
 
472
483
  ## Public token reference
473
484
 
474
- The following 84 variables are the complete public token set. Theme-independent tokens are declared on `:root`. Appearance tokens have light, dark, and system-fallback values. Derived tokens have defaults expressed in terms of other public tokens and remain overrideable.
485
+ The following 85 variables are the complete public token set. Theme-independent tokens are declared on `:root`. Appearance tokens have light, dark, and system-fallback values. Derived tokens have defaults expressed in terms of other public tokens and remain overrideable.
475
486
 
476
487
  ### Typography
477
488
 
@@ -516,17 +527,18 @@ The following 84 variables are the complete public token set. Theme-independent
516
527
 
517
528
  ### Shape, borders, and focus
518
529
 
519
- | Token | Role |
520
- | ------------------- | ----------------------------------- |
521
- | `--nk-radius-xs` | Extra-small corner radius. |
522
- | `--nk-radius-sm` | Small corner radius. |
523
- | `--nk-radius-md` | Default control corner radius. |
524
- | `--nk-radius-lg` | Large surface corner radius. |
525
- | `--nk-radius-xl` | Extra-large overlay corner radius. |
526
- | `--nk-radius-full` | Fully rounded pills and circles. |
527
- | `--nk-border-width` | Default border and separator width. |
528
- | `--nk-focus-width` | Focus-ring width. |
529
- | `--nk-focus-offset` | Focus-ring offset. |
530
+ | Token | Role |
531
+ | -------------------- | ------------------------------------------------------------------------------------------ |
532
+ | `--nk-radius-xs` | Extra-small corner radius. |
533
+ | `--nk-radius-sm` | Small corner radius. |
534
+ | `--nk-radius-md` | Default control corner radius. |
535
+ | `--nk-radius-lg` | Large surface corner radius. |
536
+ | `--nk-radius-xl` | Extra-large overlay corner radius. |
537
+ | `--nk-radius-full` | Fully rounded pills and circles. |
538
+ | `--nk-button-radius` | Optional Button-only radius override; the default `initial` preserves size-specific radii. |
539
+ | `--nk-border-width` | Default border and separator width. |
540
+ | `--nk-focus-width` | Focus-ring width. |
541
+ | `--nk-focus-offset` | Focus-ring offset. |
530
542
 
531
543
  ### Elevation and motion
532
544
 
data/docs/hotwire.md CHANGED
@@ -16,7 +16,9 @@ responses. Hotwire transports server-rendered HTML between those boundaries.
16
16
 
17
17
  Do not copy Nitro Kit controllers into the application. Do not add files under
18
18
  `app/javascript/controllers/nk`; consume the `nk--*` controllers packaged by
19
- the installed gem.
19
+ the installed gem. For browser-sensitive interactions, follow
20
+ [`browser_support.md`](browser_support.md); Nitro owns compatibility bridges for
21
+ its own components.
20
22
 
21
23
  ## Response matrix
22
24
 
@@ -30,6 +32,10 @@ the installed gem.
30
32
  Use `dom_id` or one named constant for a frame. The show, edit, invalid,
31
33
  success, and cancel responses must preserve that identifier.
32
34
 
35
+ An HTML response branch preserves the request path when Turbo is unavailable;
36
+ it is not a blanket guarantee that every surrounding component operates
37
+ without JavaScript.
38
+
33
39
  ## Forms and application Stimulus
34
40
 
35
41
  Let Turbo submit real Rails forms. Use `data-turbo-submits-with` to opt a Button
@@ -38,7 +44,11 @@ immediately; `submission_indicator: :spinner` additionally reveals an indicator
38
44
  after 1 second and lets the Button expand to fit it. Use `data-turbo-confirm` for
39
45
  compact destructive confirmation.
40
46
  Reviewed destructive flows may compose `DangerZone` and `Dialog`, but the
41
- dialog must still submit a real Rails form.
47
+ dialog must still submit a real Rails form. `data-turbo-confirm` is a Turbo
48
+ enhancement, while an ordinary server-owned review route is the no-JavaScript
49
+ confirmation path; neither an HTML response branch nor a form inside a closed
50
+ dialog supplies that interaction by itself. See the canonical classifications
51
+ in [`browser_support.md`](browser_support.md).
42
52
 
43
53
  Keep application controllers small, declarative, and disposable. For a
44
54
  self-submitting control, the complete controller can be:
@@ -68,7 +78,9 @@ permanent.
68
78
 
69
79
  Clean ephemeral UI before Turbo caches the page. Nitro Kit controllers own
70
80
  their own cache and reconnect behavior; application controllers must do the
71
- same for application-owned state.
81
+ same for application-owned state. `nk--dialog` closes an open Dialog, Sheet, or
82
+ CommandPalette on `turbo:before-cache`; reconnecting does not install listeners
83
+ or retain a mirrored open flag.
72
84
 
73
85
  ## Frame escape and recovery
74
86
 
@@ -7,7 +7,11 @@ APIs or examples.
7
7
  `2.`.
8
8
  2. Read `docs/agent_guide.md` from that installed gem, then read the locally
9
9
  installed `nitro-kit-rails`, `nitro-kit-hotwire`, and `nitro-kit-ui` skills.
10
- 3. Inspect the application before editing. Preserve established application
10
+ 3. Inspect the application before editing. Inventory user flows, every rendered
11
+ button treatment (including application-owned `.btn` classes and native Rails
12
+ helpers), joined controls, and the existing semantic color, focus, radius,
13
+ density, and typography tokens. Capture representative wide and narrow
14
+ screenshots before changing markup. Preserve established application
11
15
  conventions unless they conflict with the requested Nitro Kit 2 setup.
12
16
  4. Ensure an application base Phlex component includes `NitroKit` once and
13
17
  product components inherit from it.
@@ -25,11 +29,32 @@ APIs or examples.
25
29
  8. Use ordinary Rails routes, models, forms, and server-rendered HTML. Follow
26
30
  the installed Rails and Hotwire guidance for new work. During a migration,
27
31
  replace an existing control only when Nitro Kit 2 has a genuine semantic
28
- and behavioral equivalent. Otherwise preserve it as application-owned
29
- Rails and semantic HTML; never downgrade specialized behavior or retain
30
- copied Nitro Kit 1.x source as the fallback.
31
- 9. Run `bin/rails nitro_kit:doctor`, fix actionable failures, and run the
32
- application's relevant tests.
32
+ and behavioral equivalent. Preserve compound ownership: use `ButtonGroup`
33
+ for joined actions and `ControlGroup` for joined inputs, addons, and buttons
34
+ instead of rebuilding their geometry with a raw flex wrapper. Otherwise
35
+ preserve the control as application-owned Rails and semantic HTML; never
36
+ downgrade specialized behavior or retain copied Nitro Kit 1.x source as the
37
+ fallback. Preserve strict component boundaries: route native attributes
38
+ through `html:`, `aria:`, or `data:`, explicitly name icon-only Buttons and
39
+ triggers, and give custom `form.field` blocks explicit labels.
40
+ 9. Translate the application's existing semantic theme into documented public
41
+ `--nk-*` tokens. Preserve primary, focus, danger, neutral, font, density, and
42
+ radius decisions rather than selecting visually similar raw palette values.
43
+ Use `--nk-button-radius` when buttons intentionally have a different shape
44
+ from inputs and surfaces.
45
+ 10. Run `bin/rails nitro_kit:doctor`, fix actionable failures, and run the
46
+ application's relevant tests plus the generated upgrade smoke tests. When
47
+ the application uses strict i18n, render representative forms with
48
+ `ActiveModel::Translation.raise_on_missing_translations` enabled. Doctor
49
+ inventories migration work; a clean result is not runtime or visual
50
+ verification.
51
+ 11. Compare the same representative flows across the dated matrix in
52
+ `docs/browser_support.md`, including Mobile Safari where available, at wide
53
+ and narrow widths. Exercise keyboard focus and inspect computed styles for missing
54
+ application classes, stacked Button content, broken compound corners,
55
+ double focus rings, clipping, and theme drift. Re-audit rendered native
56
+ buttons, `button_tag`, `submit_tag`, and application-owned button classes
57
+ before declaring the migration complete.
33
58
 
34
59
  Report what you changed, any existing convention you deliberately preserved,
35
60
  any unsupported control recorded as a Nitro Kit coverage gap, and any warning
@@ -4,12 +4,12 @@ Treat a 1.x migration as a product-flow review, not a helper rename. Nitro Kit
4
4
  2 deliberately removed copied components, `nk_*` helpers, application-owned
5
5
  `controllers/nk`, and unrestricted utility-class customization.
6
6
 
7
- ## Install the 2.0 alpha before migrating
7
+ ## Install the 2.0 prerelease before migrating
8
8
 
9
- Add the 2.0 alpha prerelease to the application's Gemfile:
9
+ Add the 2.0 prerelease to the application's Gemfile:
10
10
 
11
11
  ```ruby
12
- gem "nitro_kit", "2.0.0.alpha.2"
12
+ gem "nitro_kit", "2.0.0.alpha.3"
13
13
  ```
14
14
 
15
15
  Bundler records the exact released version in `Gemfile.lock`; commit `Gemfile`
@@ -22,14 +22,19 @@ rather than a moving Git branch.
22
22
  ## Inventory behavior before editing
23
23
 
24
24
  1. List every `nk_*` helper, `NitroKit::*` component, copied Nitro source file,
25
- and `controllers/nk` controller.
25
+ `controllers/nk` controller, rendered native or Rails button helper, and
26
+ application-owned button class such as `.btn`.
26
27
  2. Group usage by user flow: authentication, settings, collection browsing,
27
28
  mobile navigation, editing, destructive actions, notifications, and
28
29
  copy/share controls.
29
30
  3. Record behavior that must survive: native element, submitted method and
30
31
  parameters, Turbo target, accessible name and description, focus behavior,
31
32
  narrow-screen presentation, empty/error state, and visual density.
32
- 4. Capture representative wide and narrow screenshots before conversion.
33
+ 4. Inventory the existing semantic primary, focus, danger, neutral, font,
34
+ density, and radius decisions. Translate those roles to public `--nk-*`
35
+ tokens rather than choosing similar raw palette values. Record separately
36
+ when buttons use a distinct shape from inputs and surfaces.
37
+ 5. Capture representative wide and narrow screenshots before conversion.
33
38
 
34
39
  If the Nitro Kit MCP catalog is connected, search it by workflow after this
35
40
  inventory — for example `mobile transcript navigation`, `settings form`, or
@@ -46,11 +51,12 @@ Map each flow to the highest-level matching 2.x component first:
46
51
  | Sign-in or recovery card | `AuthShell` |
47
52
  | Application navigation | `AppShell`, `AppNavigation` |
48
53
  | Mobile contextual navigation or details | `Sheet` |
49
- | Settings screen | `SettingsLayout`, `FormSection` |
54
+ | Settings screen | `SettingsLayout`, `SettingsSection` |
50
55
  | Empty collection card | `EmptyState` |
51
56
  | Data collection | `DataSection`, `Table`, `PaginationBar` |
52
57
  | Destructive settings | `DangerZone`, `Dialog`, `ButtonTo` |
53
58
  | Joined copy or filter controls | `ControlGroup` |
59
+ | Joined action controls | `ButtonGroup` |
54
60
 
55
61
  Only then replace remaining atoms. Common direct mappings include:
56
62
 
@@ -279,9 +285,11 @@ supported by the host's Rails Minitest and system-test setup and prints setup
279
285
  guidance for skipped files. The tests use the currently bundled gem and cover
280
286
  the shared upgrade boundary — browser-submitted Turbo validation and mutation,
281
287
  Dialog and Sheet, layout-owned Rails flash, Turbo Frame identity, redirects,
282
- and post-mutation Phlex rendering. Their collision-checked route exists only
283
- during each test and is restored afterward; they add no production route or
284
- component source. Keep application-specific migration tests for inventoried
288
+ and post-mutation Phlex rendering. Their route is prepended only during each
289
+ test, so host catch-all routes remain compatible; an exact GET or PATCH route
290
+ at the same path is still rejected rather than masked. The route is restored
291
+ afterward and adds no production route or component source. Keep
292
+ application-specific migration tests for inventoried
285
293
  product behavior alongside them.
286
294
 
287
295
  The endpoint deliberately inherits `ApplicationController` callbacks. If the
@@ -291,20 +299,52 @@ and account-selection helpers used by ordinary integration and system tests.
291
299
  Extend those application-owned classes rather than changing gem test support
292
300
  or skipping host callbacks.
293
301
 
294
- Run focused request and component tests, then compare the converted flows in a
295
- browser at wide and narrow widths. Exercise keyboard focus, dialogs and sheets,
302
+ Run focused request and component tests, then compare the converted flows
303
+ across the dated matrix in `docs/browser_support.md`, including Mobile Safari
304
+ where available, at wide and narrow widths. Exercise keyboard focus, dialogs and sheets,
296
305
  Turbo submissions, errors, empty states, light/dark appearance, and dense
297
- metadata. A green request suite does not prove that a tooltip, off-canvas
306
+ metadata. Inspect computed styles for missing application classes, stacked
307
+ Button content, broken compound corners, double focus rings, clipping, and
308
+ theme drift. A green request suite does not prove that a tooltip, off-canvas
298
309
  panel, interactive Card treatment, or responsive composition survived.
299
310
 
300
311
  Finish by deleting copied components, helpers, and controllers; run
301
312
  `bin/rails nitro_kit:doctor`; and record every remaining application-owned
302
- fallback as either intentional product UI or a Nitro Kit coverage gap.
303
-
304
- Doctor inventories only concrete Nitro Kit 1.x conventions: `nk_*` helpers,
313
+ fallback as either intentional product UI or a Nitro Kit coverage gap. Use
314
+ `bin/rails nitro_kit:doctor --format=json` when migration automation needs
315
+ stable `status`, `label`, and `detail` fields.
316
+
317
+ Before declaring the migration complete, search for every
318
+ `desperately_need_a_class:` use and review each one. Aim for zero. Move layout,
319
+ spacing, width, responsive positioning, and application colors to an
320
+ application-owned wrapper; use documented component options and native
321
+ attributes for semantics and state; accept Nitro defaults where the difference
322
+ is incidental; and keep specialized controls or navigation application-owned
323
+ when Nitro is not the right abstraction. Remove generic class forwarding from
324
+ shared builders. A scoped wrapper rule may target ordinary descendant elements
325
+ when the application truly owns that layout behavior, such as fixed table
326
+ layout. Retain the escape only when a named external integration actually
327
+ requires a class hook, and document why.
328
+
329
+ For such a retained integration, `desperately_need_a_class:` accepts Rails-style
330
+ strings, symbols, nested arrays, or conditional hashes without manual
331
+ `compact.join(" ")` formatting. A Tailwind application may use its own
332
+ Tailwind-aware merger when that hook needs conflicting utility classes
333
+ resolved. Nitro does not require the dependency because its own component CSS
334
+ is static and classless.
335
+
336
+ Doctor inventories concrete Nitro Kit 1.x conventions: `nk_*` helpers,
305
337
  generated files under `app/components/nitro_kit`, controllers under
306
- `app/javascript/controllers/nk`, the old Floating UI and combobox packages,
307
- and `tailwind_merge`. Every finding includes a file and replacement. Its
338
+ `app/javascript/controllers/nk`, and the old Floating UI and combobox packages.
339
+ When application CSS defines a `.btn` treatment, Doctor
340
+ also records rendered `btn` class usages as application-owned review work; it
341
+ does not assume every specialized control should become a Nitro Button. Doctor
342
+ also uses Ruby syntax trees to catch provable 2.0 runtime violations: direct
343
+ `id:` keywords on Table compound methods and statically icon-only
344
+ `NitroKit::Button`, Dropdown trigger, and Sheet trigger declarations without an
345
+ accessible name. Rendering remains
346
+ the final authority for dynamic wrappers and delegated component declarations.
347
+ Every finding includes a file and replacement or review instruction. Its
308
348
  disposition is:
309
349
 
310
350
  - `migrated` — no remaining occurrence in that category.
@@ -314,5 +354,7 @@ disposition is:
314
354
  an application namespace, not as a Nitro shadow.
315
355
 
316
356
  The inventory deliberately does not guess from generic component, JavaScript,
317
- or dependency names. Review application-owned product behavior separately and
318
- keep its migration record with the application.
357
+ or dependency names beyond a button treatment the application itself defines.
358
+ Review application-owned product behavior separately and keep its migration
359
+ record with the application. Re-audit native buttons, Rails button helpers, and
360
+ application-owned button classes after Doctor is otherwise clean.
@@ -15,11 +15,11 @@ replaced later.
15
15
  - One wrapper immediately inside `shell.main` owns responsive page padding for
16
16
  every route; the shell owns viewport height and scrolling.
17
17
  - Infrequent account destinations go after `navigation.spacer`; settings
18
- compose with `SettingsLayout` and plain `FormSection` regions.
18
+ compose with `SettingsLayout` and plain `SettingsSection` regions.
19
19
  - When destination count warrants search, compose one `CommandPalette` in the
20
20
  shell and render only routes the current membership may visit.
21
21
  - Centralize cross-cutting feedback in one flash-driven toast region rendered
22
- by the layout.
22
+ by the layout, and set the document language on the root `html` element.
23
23
 
24
24
  ## Use memberships from the first user
25
25
 
@@ -46,7 +46,9 @@ primary destinations in `AppNavigation`; place route titles and persistent
46
46
  basic actions in the shell `Toolbar`. One wrapper immediately inside
47
47
  `shell.main` owns responsive page padding for every route. The topbar and
48
48
  sidebar header should use the shell's shared height and border tokens rather
49
- than independent padding guesses.
49
+ than independent padding guesses. At narrow widths, allow trailing actions to
50
+ stack below the Back affordance and title so neither the title nor persistent
51
+ actions are clipped.
50
52
 
51
53
  Put infrequent account navigation after `navigation.spacer`, near the account
52
54
  controls at the bottom of the sidebar:
@@ -91,7 +93,7 @@ its major regions.
91
93
 
92
94
  Use `SettingsLayout` inside the normal shell main region. Its navigation lists
93
95
  stable subsections such as Profile, Notifications, Appearance, and Password;
94
- its content renders the selected form. Use `FormSection` for genuinely distinct
96
+ its content renders the selected form. Use `SettingsSection` for genuinely distinct
95
97
  form regions and ordinary whitespace or dividers between them.
96
98
 
97
99
  Render subsection destinations as links and mark the active link with
@@ -17,6 +17,8 @@ and their tests together.
17
17
  `DataSection`, Card, and caption.
18
18
  - Child routes add one compact icon Back link before the title instead of a
19
19
  trailing Cancel action.
20
+ - At narrow widths, trailing actions stack below the Back affordance and title;
21
+ do not clip the title or hide persistent actions to keep the header short.
20
22
  - One responsive padding rule on the main wrapper owns the page gutter;
21
23
  data-heavy hybrid CRUD content uses the full available width.
22
24
 
@@ -31,7 +33,8 @@ Put a `Toolbar` in `shell.topbar`. Its leading region contains the route's one
31
33
  `h1`; its trailing region contains basic actions such as New, Edit, Cancel,
32
34
  Save, Publish, or View. A Button outside a form can submit it through the
33
35
  native `form:` attribute. This keeps the same action hierarchy on narrow and
34
- wide screens without custom JavaScript.
36
+ wide screens without custom JavaScript. Nitro Kit stacks the regions at narrow
37
+ widths so a Back affordance, long title, and several actions remain legible.
35
38
 
36
39
  ```ruby
37
40
  AppShell(id: "admin", layout: :hybrid) do |shell|
@@ -70,7 +73,7 @@ Do not add viewport height or another outer padding rule to `admin-main`; the
70
73
  shell owns viewport geometry and the wrapper owns the one page gutter. Use the
71
74
  same shell and gutter on team administration and settings routes.
72
75
  Place a bottom-anchored Settings destination after `AppNavigation#spacer`, then
73
- compose settings subsections with `SettingsLayout` and plain `FormSection`
76
+ compose settings subsections with `SettingsLayout` and plain `SettingsSection`
74
77
  regions. Settings destinations are links with `aria-current`, not action
75
78
  Buttons. Read `application_foundation.md` for the complete application frame.
76
79
 
@@ -90,7 +93,7 @@ renders its table and pagination directly. Use `DataSection` when a page has
90
93
  multiple independently named datasets. At zero records, replace the data region
91
94
  with one intentional `EmptyState`; keep the primary New action in the toolbar.
92
95
 
93
- A new or edit page begins with `FormSection`. Use one form component for new,
96
+ A new or edit page begins with `SettingsSection`. Use one form component for new,
94
97
  edit, and invalid renders. Put the primary submit in the toolbar by setting the
95
98
  button's `form:` to the form's stable DOM ID. Invalid submissions render the
96
99
  same model and form with `422 Unprocessable Entity`. The toolbar owns that
@@ -2,6 +2,11 @@
2
2
 
3
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.
4
4
 
5
+ Nitro keeps the declarative dialog command as the native path and supplies a
6
+ capability-detected JavaScript bridge for supported browsers that lack Invoker
7
+ Commands. The bridge changes only disclosure; it never intercepts or recreates
8
+ the form request.
9
+
5
10
  ## Summary
6
11
 
7
12
  - Use a native Nitro `Dialog` only when the user must review impact or type a
@@ -100,6 +105,26 @@ end
100
105
 
101
106
  Do not stack `turbo_confirm` inside a Dialog. Choose one confirmation surface.
102
107
 
108
+ ## Browser fallback
109
+
110
+ The reviewed flow depends on Dialog opening in the user's browser. Nitro owns
111
+ the compatibility path when its packaged controller is installed, but no
112
+ client-side dialog can guarantee confirmation when both JavaScript and the
113
+ required native dialog invocation API are unavailable. Applications that must
114
+ support that case should link to a server-rendered review route with an ordinary
115
+ deletion form.
116
+
117
+ `data-turbo-confirm` is a compact Turbo enhancement, not that no-JavaScript
118
+ fallback. Without Turbo it does not display a confirmation, although the real
119
+ form can still submit. When confirmation must be unavoidable, make review a
120
+ server-owned step rather than stacking both client confirmation surfaces.
121
+
103
122
  ## Tests
104
123
 
105
124
  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.
125
+
126
+ The successful `DELETE` response must redirect with `303 See Other`, including
127
+ when Turbo submits the form. Without JavaScript the real form still provides a
128
+ request path, but a form placed only inside a closed dialog is reachable only
129
+ where Invoker Commands are supported; provide an ordinary server-rendered link
130
+ to a review page when deletion must remain reachable across that baseline.
@@ -37,7 +37,7 @@ module UI
37
37
  end
38
38
 
39
39
  def view_template
40
- render NitroKit::FormSection.new(
40
+ render NitroKit::SettingsSection.new(
41
41
  title: project.persisted? ? "Edit project" : "New project",
42
42
  description: "Project details are visible to every workspace member."
43
43
  ) do |section|
@@ -50,6 +50,9 @@ public.
50
50
  ## Use ordinary Rails responses
51
51
 
52
52
  - Use `form_with` and `NitroKit::FormBuilder` for model-backed forms.
53
+ - Treat `month_field` and `week_field` as progressive enhancement. Validate
54
+ `YYYY-MM` and `YYYY-Www`, ranges, and increments in the model; use an
55
+ application-owned Select with explicit options when exact selection matters.
53
56
  - Redirect successful non-GET submissions with `status: :see_other`.
54
57
  - Render the same invalid model with `status: :unprocessable_entity`.
55
58
  - Keep an HTML response for every Turbo-enhanced flow.
@@ -68,7 +71,7 @@ Do not repeat the route title in a `PageHeader`, section heading, card title,
68
71
  and table caption. Use `PageHeader` inside an application shell only when the
69
72
  page needs a genuinely content-led introduction beyond the toolbar title. A
70
73
  single-table index renders the table directly. A form page begins with the
71
- actual `FormSection`. A detail page begins with status or metadata. Use a Card
74
+ actual `SettingsSection`. A detail page begins with status or metadata. Use a Card
72
75
  only for a bounded object that needs a surface, never as the automatic wrapper
73
76
  for every region.
74
77
 
@@ -4,10 +4,10 @@ Nitro Kit 2.0 uses Rails where Rails owns important application semantics: model
4
4
 
5
5
  ## Installation and assets
6
6
 
7
- Add the 2.0 alpha prerelease to the application's Gemfile:
7
+ Add the 2.0 prerelease to the application's Gemfile:
8
8
 
9
9
  ```ruby
10
- gem "nitro_kit", "2.0.0.alpha.2"
10
+ gem "nitro_kit", "2.0.0.alpha.3"
11
11
  ```
12
12
 
13
13
  Bundler records the exact released version in `Gemfile.lock`; commit `Gemfile`
@@ -53,7 +53,7 @@ stylesheet_link_tag("lexxy", "nitro_kit", "application", data: { turbo_track: "r
53
53
  There is no component source-copy step. Re-run the generator after upgrading
54
54
  the gem, then run `bin/rails nitro_kit:doctor`. Doctor fails for a missing,
55
55
  duplicate, or misordered bootstrap or stylesheet entry and reports the exact
56
- repair.
56
+ repair. Use `--format=json` for structured automation output.
57
57
 
58
58
  Install a focused upgrade acceptance flow separately:
59
59
 
@@ -67,8 +67,9 @@ The generator creates each test only when its path is absent and its host
67
67
  prerequisite exists: `test/test_helper.rb` for integration coverage and
68
68
  `test/application_system_test_case.rb` for browser coverage. It prints an
69
69
  actionable message for unsupported pieces and never overwrites
70
- application-owned coverage. Using the currently bundled gem, the tests install
71
- a collision-checked route only for each test, then restore host routing. The
70
+ application-owned coverage. Using the currently bundled gem, the tests prepend
71
+ a route only for each test, then restore host routing. Catch-all routes remain
72
+ compatible, while an exact GET or PATCH collision is rejected. The
72
73
  endpoint inherits the host `ApplicationController` and renders through the
73
74
  application layout, so Nitro CSS, the appearance bootstrap, host JavaScript and
74
75
  CSP handling remain in the exercised path. Coverage includes browser-submitted
@@ -108,9 +109,24 @@ the gem controller, because that would bypass the integration under test.
108
109
 
109
110
  ## Stimulus and importmap
110
111
 
111
- Enhanced components use gem-owned Stimulus controllers, including `nk--app-shell`, `nk--appearance`, `nk--avatar`, `nk--checkable`, `nk--combobox`, `nk--dropdown`, `nk--dropzone`, `nk--progressive-image`, `nk--tabs`, `nk--toast`, and `nk--tooltip`.
112
-
113
- Accordion and Dialog are controller-free: native `details` grouping and declarative `command`/`commandfor` own their complete interaction. Dropdown uses native Popover as its source of truth and adds only menu keyboard focus; Tooltip uses CSS for hover/focus and JavaScript only for Escape dismissal. Nitro does not promise dialog light dismiss.
112
+ Enhanced components use gem-owned Stimulus controllers, including `nk--app-shell`, `nk--appearance`, `nk--avatar`, `nk--checkable`, `nk--combobox`, `nk--dialog`, `nk--dropdown`, `nk--dropzone`, `nk--progressive-image`, `nk--tabs`, `nk--toast`, and `nk--tooltip`.
113
+
114
+ Accordion disclosure is controller-free. Its shared `name` is the native
115
+ single-group authority; browsers without named-details grouping retain full
116
+ disclosure but have the documented reduced, independently-open single-mode
117
+ baseline. Dialog and Sheet prefer declarative
118
+ `command`/`commandfor`, while `nk--dialog` owns backdrop and cancel policy. The
119
+ controller checks each invoker's reflected relationship and uses
120
+ `HTMLDialogElement.showModal()` or `close()` only when the native command will
121
+ not run. CommandPalette uses the same bridge instead of replacing the native
122
+ path. Without JavaScript, these controls still require native Invoker Commands.
123
+ Server-rendered content inside a closed overlay is therefore not itself a
124
+ JavaScript-free fallback; critical content needs an ordinary server review
125
+ route. The canonical classification matrix is in
126
+ [`browser_support.md`](browser_support.md).
127
+ Dropdown uses native Popover as its source of truth and adds menu keyboard focus
128
+ and positioning; Tooltip uses CSS for hover/focus and JavaScript only for Escape
129
+ dismissal. See the [browser support policy](browser_support.md).
114
130
 
115
131
  When `importmap-rails` is present, the engine adds its importmap and asset paths automatically. The application must still install Stimulus and provide the normal controller loader:
116
132
 
@@ -121,7 +137,10 @@ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
121
137
  eagerLoadControllersFrom("controllers", application);
122
138
  ```
123
139
 
124
- Nitro Kit packages no third-party JavaScript. Accordion, Dialog, date inputs, and Switch use native browser behavior and need no controllers.
140
+ Nitro Kit packages no third-party JavaScript. Accordion disclosure, date inputs,
141
+ and Switch use native browser behavior. Dialog and Sheet mount their packaged
142
+ controller for dismissal policy, Turbo cache cleanup, and the feature-detected
143
+ Invoker Commands bridge.
125
144
 
126
145
  The engine deliberately boots when importmap is absent. In that configuration, Ruby and CSS remain available, but automatic JavaScript registration does not: a bundler-based application must expose and register the controller modules itself. Nitro Kit 2.0 does not ship a JavaScript-package entrypoint.
127
146
 
@@ -208,11 +227,11 @@ render NitroKit::CommandPalette.new(id: "workspace-search", label: "Search works
208
227
  end
209
228
  ```
210
229
 
211
- The application remains responsible for authorization and must render only destinations the current user may visit. Native dialog commands and links provide the baseline; Stimulus adds filtering and the Command-K or Control-K shortcut. Use `shortcut: false` for any additional palette on the same document so only one component owns the global shortcut.
230
+ The application remains responsible for authorization and must render only destinations the current user may visit. Stimulus adds filtering and the Command-K or Control-K shortcut. Without JavaScript, the trigger and destination links are available only where declarative dialog commands are supported. Use `shortcut: false` for any additional palette on the same document so only one component owns the global shortcut.
212
231
 
213
232
  ### Server-rendered command palette results
214
233
 
215
- For a large or dynamic destination set, pass a GET endpoint through `search_url:`. Keep a useful authorized set in the declaration block: those links are the no-JavaScript baseline and the immediate first render.
234
+ For a large or dynamic destination set, pass a GET endpoint through `search_url:`. Keep a useful authorized set in the declaration block: those links are the immediate first render and the no-JavaScript baseline in browsers with declarative dialog commands.
216
235
 
217
236
  ```ruby
218
237
  render NitroKit::CommandPalette.new(
@@ -350,6 +369,15 @@ The complete builder surface includes:
350
369
  - `submit` and `button`.
351
370
  - Rails-shaped color, date, datetime, email, file, month, number, password, phone/telephone, range, rich text, search, text, textarea, time, URL, and week fields.
352
371
 
372
+ `month_field` and `week_field` emit native `month` and `week` input types as
373
+ progressive enhancement. Some supported desktop browsers expose ordinary text
374
+ entry, and older iOS Safari releases lack week selection, so do not rely on a
375
+ picker, browser normalization, or `min`, `max`, and `step`. Validate `YYYY-MM`
376
+ and `YYYY-Www` plus domain range and increment rules on the model. If the user
377
+ must choose from an exact bounded set, render `form.field(..., as: :select,
378
+ options: ...)` with application-owned options. Nitro does not ship a generic
379
+ datepicker for this case.
380
+
353
381
  `submit` renders a primary submit Button named `commit`, and `button` defaults
354
382
  to `type: :submit`. The Rails helpers Nitro does not style — `label`,
355
383
  `collection_select`, `grouped_collection_select`, `collection_radio_buttons`,
@@ -9,6 +9,12 @@ module NitroKit
9
9
  prerequisite: "test/test_helper.rb",
10
10
  remedy: "Install Rails' Minitest test infrastructure, then rerun this generator.",
11
11
  content: <<~RUBY
12
+ # Generated by Nitro Kit as a host-integration regression check.
13
+ #
14
+ # This exercises the bundled Nitro Kit through this application's layout,
15
+ # ApplicationController callbacks, Rails forms, Turbo, and flash rendering.
16
+ # It does not replace acceptance tests for the application's migrated flows.
17
+
12
18
  require "test_helper"
13
19
  require "nitro_kit/upgrade_smoke_test"
14
20
 
@@ -26,6 +32,12 @@ module NitroKit
26
32
  prerequisite: "test/application_system_test_case.rb",
27
33
  remedy: "Run `bin/rails generate system_test`, configure a browser driver, then rerun this generator.",
28
34
  content: <<~RUBY
35
+ # Generated by Nitro Kit as a host-integration regression check.
36
+ #
37
+ # This exercises the bundled Nitro Kit through this application's layout,
38
+ # ApplicationController callbacks, Rails forms, Turbo, and browser runtime.
39
+ # It does not replace acceptance tests for the application's migrated flows.
40
+
29
41
  require "application_system_test_case"
30
42
  require "nitro_kit/upgrade_smoke_test"
31
43
 
@@ -152,12 +152,17 @@ module NitroKit
152
152
  end
153
153
 
154
154
  def stimulus_loader_check
155
- path = application_root.join("app/javascript/controllers/index.js")
156
- ready = path.exist? && path.read.include?("eagerLoadControllersFrom")
155
+ javascript = application_root.glob("app/javascript/**/*.{js,ts}").select(&:file?).map(&:read)
156
+ loader = if javascript.any? { _1.include?("eagerLoadControllersFrom") }
157
+ "eager controller loading"
158
+ elsif javascript.any? { _1.match?(/\bapplication\.register\s*\(/) }
159
+ "explicit controller registration"
160
+ end
161
+
157
162
  Check.new(
158
- status: ready ? :pass : :warn,
163
+ status: loader ? :pass : :warn,
159
164
  label: "Stimulus loader",
160
- detail: ready ? "eager controller loading is configured" : "verify the application's controller registration"
165
+ detail: loader ? "#{loader} is configured" : "verify the application's controller registration"
161
166
  )
162
167
  end
163
168