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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +229 -1
  3. data/README.md +25 -10
  4. data/STYLE_GUIDE.md +47 -5
  5. data/app/assets/stylesheets/nitro_kit-tailwind-v4.css +3 -3
  6. data/app/assets/stylesheets/nitro_kit.css +2820 -1816
  7. data/app/components/nitro_kit/accordion.rb +2 -26
  8. data/app/components/nitro_kit/alert.rb +1 -11
  9. data/app/components/nitro_kit/app_navigation.rb +45 -11
  10. data/app/components/nitro_kit/app_shell.rb +1 -7
  11. data/app/components/nitro_kit/appearance_picker.rb +11 -12
  12. data/app/components/nitro_kit/avatar.rb +6 -1
  13. data/app/components/nitro_kit/avatar_stack.rb +3 -0
  14. data/app/components/nitro_kit/badge.rb +13 -4
  15. data/app/components/nitro_kit/button_group.rb +2 -2
  16. data/app/components/nitro_kit/checkbox.rb +7 -4
  17. data/app/components/nitro_kit/checkbox_group.rb +0 -25
  18. data/app/components/nitro_kit/combobox.rb +4 -33
  19. data/app/components/nitro_kit/command_palette.rb +42 -59
  20. data/app/components/nitro_kit/component.rb +57 -5
  21. data/app/components/nitro_kit/danger_zone.rb +5 -1
  22. data/app/components/nitro_kit/data_section.rb +28 -4
  23. data/app/components/nitro_kit/details_table.rb +19 -8
  24. data/app/components/nitro_kit/dialog.rb +23 -19
  25. data/app/components/nitro_kit/dropdown.rb +1 -6
  26. data/app/components/nitro_kit/dropzone.rb +4 -15
  27. data/app/components/nitro_kit/empty_state.rb +21 -2
  28. data/app/components/nitro_kit/field.rb +35 -11
  29. data/app/components/nitro_kit/form_builder.rb +1 -2
  30. data/app/components/nitro_kit/progressive_image.rb +2 -2
  31. data/app/components/nitro_kit/radio_button.rb +7 -4
  32. data/app/components/nitro_kit/radio_button_group.rb +0 -25
  33. data/app/components/nitro_kit/select.rb +4 -17
  34. data/app/components/nitro_kit/settings_layout.rb +17 -4
  35. data/app/components/nitro_kit/{form_section.rb → settings_section.rb} +18 -11
  36. data/app/components/nitro_kit/sheet.rb +29 -20
  37. data/app/components/nitro_kit/stat_grid.rb +18 -2
  38. data/app/components/nitro_kit/table.rb +22 -3
  39. data/app/components/nitro_kit/tabs.rb +2 -13
  40. data/app/components/nitro_kit/toast.rb +4 -2
  41. data/app/components/nitro_kit/toolbar.rb +2 -0
  42. data/app/components/nitro_kit/tooltip.rb +7 -10
  43. data/app/javascript/controllers/nk/app_shell_controller.js +3 -3
  44. data/app/javascript/controllers/nk/appearance_controller.js +6 -0
  45. data/app/javascript/controllers/nk/combobox_controller.js +51 -1
  46. data/app/javascript/controllers/nk/command_palette_controller.js +25 -11
  47. data/app/javascript/controllers/nk/dialog_controller.js +54 -0
  48. data/app/javascript/controllers/nk/dropdown_controller.js +45 -0
  49. data/app/javascript/controllers/nk/dropzone_controller.js +2 -2
  50. data/app/javascript/controllers/nk/progressive_image_controller.js +11 -7
  51. data/app/javascript/controllers/nk/tabs_controller.js +21 -2
  52. data/app/javascript/controllers/nk/tooltip_controller.js +9 -0
  53. data/config/locales/en.yml +4 -1
  54. data/docs/agent_guide.md +63 -123
  55. data/docs/agent_native_spec.md +72 -383
  56. data/docs/browser_support.md +82 -0
  57. data/docs/component_contracts.md +108 -82
  58. data/docs/customization.md +689 -91
  59. data/docs/hotwire.md +50 -76
  60. data/docs/initialization_prompt.md +26 -31
  61. data/docs/migration_1_to_2.md +88 -30
  62. data/docs/patterns/application_foundation.md +45 -119
  63. data/docs/patterns/crud_resource.md +44 -126
  64. data/docs/patterns/destructive_action.md +45 -77
  65. data/docs/patterns/flash_and_toast.md +18 -36
  66. data/docs/patterns/inline_edit.md +21 -44
  67. data/docs/patterns/queryable_collection.md +65 -161
  68. data/docs/patterns/resource_form.md +33 -81
  69. data/docs/rails_conventions.md +38 -75
  70. data/docs/rails_integration.md +96 -446
  71. data/lib/generators/nitro_kit/upgrade_smoke_tests_generator.rb +12 -0
  72. data/lib/nitro_kit/installation.rb +20 -7
  73. data/lib/nitro_kit/migration_inventory.rb +275 -8
  74. data/lib/nitro_kit/upgrade_smoke_test.rb +10 -12
  75. data/lib/nitro_kit/version.rb +1 -1
  76. data/lib/rails/commands/nitro_kit/nitro_kit_command.rb +10 -3
  77. data/plugins/nitro-kit/skills/nitro-kit-hotwire/SKILL.md +4 -0
  78. data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +13 -1
  79. data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +32 -15
  80. data/src/stylesheets/nitro_kit/components/accordion.css +44 -44
  81. data/src/stylesheets/nitro_kit/components/alert.css +27 -36
  82. data/src/stylesheets/nitro_kit/components/app_navigation.css +92 -20
  83. data/src/stylesheets/nitro_kit/components/app_shell.css +199 -196
  84. data/src/stylesheets/nitro_kit/components/appearance_picker.css +61 -39
  85. data/src/stylesheets/nitro_kit/components/auth_shell.css +2 -2
  86. data/src/stylesheets/nitro_kit/components/avatar.css +15 -11
  87. data/src/stylesheets/nitro_kit/components/avatar_stack.css +48 -29
  88. data/src/stylesheets/nitro_kit/components/badge.css +13 -9
  89. data/src/stylesheets/nitro_kit/components/button.css +121 -79
  90. data/src/stylesheets/nitro_kit/components/button_group.css +19 -14
  91. data/src/stylesheets/nitro_kit/components/card.css +12 -6
  92. data/src/stylesheets/nitro_kit/components/checkbox.css +54 -67
  93. data/src/stylesheets/nitro_kit/components/checkbox_group.css +16 -16
  94. data/src/stylesheets/nitro_kit/components/combobox.css +39 -29
  95. data/src/stylesheets/nitro_kit/components/command_palette.css +110 -51
  96. data/src/stylesheets/nitro_kit/components/container.css +6 -6
  97. data/src/stylesheets/nitro_kit/components/control_group.css +61 -67
  98. data/src/stylesheets/nitro_kit/components/danger_zone.css +8 -8
  99. data/src/stylesheets/nitro_kit/components/data_section.css +4 -4
  100. data/src/stylesheets/nitro_kit/components/details_table.css +17 -15
  101. data/src/stylesheets/nitro_kit/components/dialog.css +8 -5
  102. data/src/stylesheets/nitro_kit/components/dropdown.css +36 -30
  103. data/src/stylesheets/nitro_kit/components/dropzone.css +78 -35
  104. data/src/stylesheets/nitro_kit/components/empty_state.css +7 -7
  105. data/src/stylesheets/nitro_kit/components/field.css +32 -28
  106. data/src/stylesheets/nitro_kit/components/field_group.css +13 -0
  107. data/src/stylesheets/nitro_kit/components/fieldset.css +8 -2
  108. data/src/stylesheets/nitro_kit/components/flex.css +1 -1
  109. data/src/stylesheets/nitro_kit/components/grid.css +1 -1
  110. data/src/stylesheets/nitro_kit/components/icon.css +5 -5
  111. data/src/stylesheets/nitro_kit/components/input.css +42 -8
  112. data/src/stylesheets/nitro_kit/components/layout.css +165 -165
  113. data/src/stylesheets/nitro_kit/components/page_header.css +7 -7
  114. data/src/stylesheets/nitro_kit/components/pagination.css +58 -49
  115. data/src/stylesheets/nitro_kit/components/pagination_bar.css +9 -9
  116. data/src/stylesheets/nitro_kit/components/palette.css +145 -77
  117. data/src/stylesheets/nitro_kit/components/progressive_image.css +45 -55
  118. data/src/stylesheets/nitro_kit/components/radio_button.css +48 -46
  119. data/src/stylesheets/nitro_kit/components/radio_button_group.css +49 -49
  120. data/src/stylesheets/nitro_kit/components/rich_text_area.css +16 -1
  121. data/src/stylesheets/nitro_kit/components/select.css +24 -15
  122. data/src/stylesheets/nitro_kit/components/settings_layout.css +26 -23
  123. data/src/stylesheets/nitro_kit/components/settings_section.css +84 -0
  124. data/src/stylesheets/nitro_kit/components/sheet.css +30 -10
  125. data/src/stylesheets/nitro_kit/components/stat_grid.css +11 -10
  126. data/src/stylesheets/nitro_kit/components/switch.css +60 -62
  127. data/src/stylesheets/nitro_kit/components/table.css +43 -35
  128. data/src/stylesheets/nitro_kit/components/tabs.css +48 -40
  129. data/src/stylesheets/nitro_kit/components/textarea.css +20 -5
  130. data/src/stylesheets/nitro_kit/components/toast.css +11 -47
  131. data/src/stylesheets/nitro_kit/components/toolbar.css +16 -41
  132. data/src/stylesheets/nitro_kit/components/tooltip.css +38 -29
  133. data/src/stylesheets/nitro_kit/components/typeset.css +150 -4
  134. data/src/stylesheets/nitro_kit/reset.css +15 -15
  135. data/src/stylesheets/nitro_kit/tokens.css +501 -79
  136. metadata +4 -4
  137. data/docs/new_app_strategy.md +0 -22
  138. data/src/stylesheets/nitro_kit/components/form_section.css +0 -71
data/docs/hotwire.md CHANGED
@@ -1,94 +1,68 @@
1
1
  # Hotwire with Nitro Kit
2
2
 
3
- Nitro Kit owns component markup, CSS, and its focused progressive controllers.
4
- Rails owns records, routes, authorization, queries, DOM identity, and server
5
- responses. Hotwire transports server-rendered HTML between those boundaries.
3
+ **Audience:** Application developers and coding agents implementing Hotwire
4
+ interactions with Nitro Kit.
6
5
 
7
- ## Choose the smallest interaction
8
-
9
- 1. Use ordinary links and forms under Turbo Drive.
10
- 2. Use a Turbo Frame for one independently navigable or replaceable region.
11
- 3. Return request-scoped Turbo Streams when one action changes multiple
12
- regions.
13
- 4. Broadcast only when another session needs the update.
14
- 5. Add application Stimulus only for browser-owned behavior the preceding
15
- layers cannot express.
16
-
17
- Do not copy Nitro Kit controllers into the application. Do not add files under
18
- `app/javascript/controllers/nk`; consume the `nk--*` controllers packaged by
19
- the installed gem.
20
-
21
- ## Response matrix
6
+ Rails owns records, routes, authorization, DOM identity, and responses. Nitro
7
+ Kit owns component markup, CSS, and its focused controllers. Hotwire transports
8
+ server-rendered HTML between them.
22
9
 
23
- | Request | Success | Invalid or denied |
24
- | -------------------- | --------------------------------------------- | ------------------------------------------------------------------- |
25
- | HTML form mutation | Redirect with `303 See Other` | Render HTML with `422` |
26
- | Frame form mutation | Redirect or render the same frame ID | Render the same frame ID with `422` |
27
- | Stream form mutation | Return only when multiple targets must change | Render the invalid form target with `422` and keep an HTML fallback |
28
- | GET query | Render from URL parameters | Render a useful empty or error state |
29
-
30
- Use `dom_id` or one named constant for a frame. The show, edit, invalid,
31
- success, and cancel responses must preserve that identifier.
32
-
33
- ## Forms and application Stimulus
10
+ ## Choose the smallest interaction
34
11
 
35
- Let Turbo submit real Rails forms. Use `data-turbo-submits-with` to opt a Button
36
- into submission feedback without replacing its visible label. The Button dims
37
- immediately; `submission_indicator: :spinner` additionally reveals an indicator
38
- after 1 second and lets the Button expand to fit it. Use `data-turbo-confirm` for
39
- compact destructive confirmation.
40
- Reviewed destructive flows may compose `DangerZone` and `Dialog`, but the
41
- dialog must still submit a real Rails form.
12
+ 1. Ordinary links and forms under Turbo Drive.
13
+ 2. One Turbo Frame for one independently navigable region.
14
+ 3. A request-scoped Turbo Stream when one action changes multiple regions.
15
+ 4. A broadcast only when another session needs the update.
16
+ 5. Application Stimulus only for browser-owned behavior the previous layers
17
+ cannot express.
42
18
 
43
- Keep application controllers small, declarative, and disposable. For a
44
- self-submitting control, the complete controller can be:
19
+ Do not copy Nitro controllers or add files under
20
+ `app/javascript/controllers/nk`. Follow the canonical
21
+ [browser support policy](browser_support.md) for fallback behavior.
45
22
 
46
- ```js
47
- import { Controller } from "@hotwired/stimulus";
23
+ ## Response contract
48
24
 
49
- export default class extends Controller {
50
- submit() {
51
- this.element.requestSubmit();
52
- }
53
- }
54
- ```
25
+ | Request | Success | Validation failure |
26
+ | --------------- | ----------------------------------------- | --------------------------------------------------------------- |
27
+ | HTML mutation | Redirect with `303 See Other` | Render the invalid form with `422` |
28
+ | Frame mutation | Redirect to, or render, the same frame ID | Render the same frame ID with `422` |
29
+ | Stream mutation | Return a stream only for multiple targets | Replace the invalid form target with `422`; keep an HTML branch |
30
+ | GET query | Render from URL parameters | Render a useful empty or error state |
55
31
 
56
- Attach `data-action="change->auto-submit#submit"` to the form so change events
57
- bubble to that one controller root. Keep a submit button inside `noscript` as
58
- the HTML fallback. Use `data-action` instead of manually registering DOM listeners. If a
59
- controller owns a listener, observer, timer, object URL, or third-party
60
- instance, release it in `disconnect`.
32
+ Authentication and authorization failures are separate policy decisions; do
33
+ not return `422` for them.
61
34
 
62
- ## Morphing and cache lifecycle
35
+ Use `dom_id` or one named constant for each frame. Show, edit, invalid, success,
36
+ and cancel responses must preserve that identifier. An HTML branch preserves
37
+ the request path without Turbo; it does not make a closed or JavaScript-owned
38
+ interaction available.
63
39
 
64
- Default to morphable server-rendered HTML. Use `data-turbo-permanent` only for
65
- a genuinely stateful island, always with a stable unique `id`. Update content
66
- inside a permanent element deliberately instead of making broad page regions
67
- permanent.
40
+ ## Stimulus and lifecycle
68
41
 
69
- Clean ephemeral UI before Turbo caches the page. Nitro Kit controllers own
70
- their own cache and reconnect behavior; application controllers must do the
71
- same for application-owned state.
42
+ Let Turbo submit real Rails forms. Use `data-turbo-submits-with` for submission
43
+ feedback and `data-turbo-confirm` only for compact confirmation. A reviewed
44
+ destructive flow still submits a real Rails form; use the
45
+ [destructive action pattern](patterns/destructive_action.md).
72
46
 
73
- ## Frame escape and recovery
47
+ Keep application controllers declarative. Prefer `data-action` over manually
48
+ registered listeners. Release listeners, observers, timers, object URLs, and
49
+ third-party instances in `disconnect`.
74
50
 
75
- An authentication redirect or error response inside a frame must not strand
76
- the user behind a missing-frame error. Use `_top` when navigation must leave
77
- the frame. Keep authentication and authorization handling capable of returning
78
- a full-page response, and handle `turbo:frame-missing` only when the
79
- application has a deliberate recovery policy.
51
+ Keep server-rendered markup morph-safe. Enable refresh morphing deliberately,
52
+ and use `data-turbo-permanent` only for a stateful island with a stable unique
53
+ ID. Clean ephemeral UI before Turbo caches a page.
80
54
 
81
- Give lazy frames meaningful loading content. A failed frame should leave an
82
- understandable state and a path to retry or continue without JavaScript.
55
+ Use `_top` when navigation must leave a frame. Authentication redirects and
56
+ errors must not strand the user behind a missing-frame response. Give lazy and
57
+ failed frames useful content and a route to continue.
83
58
 
84
- ## Verify behavior
59
+ ## Verify
85
60
 
86
61
  - Request-test `303`, `422`, HTML fallback, and stable frame IDs.
87
- - On mutation responses, assert submitted content inside the response's
88
- matching frame, not merely somewhere in the response body.
89
- - System-test focus, dialogs, frame navigation, and multi-target changes.
90
- - Use Capybara assertions that wait for the DOM; never use `sleep`.
91
- - Test navigation, morphing, and reconnection without duplicating controller
92
- roots or listeners.
93
-
94
- Read the matching recipe under `docs/patterns/` for complete compositions.
62
+ - Scope mutation assertions to the matching frame.
63
+ - System-test focus, navigation, dialogs, and multi-target changes.
64
+ - Use Capybara waiting assertions; never use `sleep`.
65
+ - Test navigation, caching, morphing, and reconnection for duplicate state or
66
+ listeners.
67
+
68
+ Use the matching [interaction pattern](patterns/) for complete compositions.
@@ -1,36 +1,31 @@
1
- # Initialize Nitro Kit 2 in this Rails application
1
+ # Verify Nitro Kit 2 setup in this Rails application
2
2
 
3
- Finish the application-specific Nitro Kit 2 setup. Do not use Nitro Kit 1.x
4
- APIs or examples.
3
+ **Audience:** Coding agent running immediately after Nitro Kit installation.
5
4
 
6
- 1. Run `bundle show nitro_kit` and confirm the resolved version begins with
5
+ 1. Run `bundle show nitro_kit` and confirm the resolved version starts with
7
6
  `2.`.
8
- 2. Read `docs/agent_guide.md` from that installed gem, then read the locally
9
- installed `nitro-kit-rails`, `nitro-kit-hotwire`, and `nitro-kit-ui` skills.
10
- 3. Inspect the application before editing. Preserve established application
11
- conventions unless they conflict with the requested Nitro Kit 2 setup.
12
- 4. Ensure an application base Phlex component includes `NitroKit` once and
13
- product components inherit from it.
14
- 5. Re-run `bin/rails generate nitro_kit:install`. Ensure its application layout
15
- setup has one appearance bootstrap before every stylesheet, then optional
16
- third-party base styles, the optional Tailwind adapter, `nitro_kit`, compiled
17
- Tailwind, and application token overrides in that order. Add flash toast
18
- rendering when the application uses it.
19
- 6. Ensure Turbo and Stimulus are wired and the normal Stimulus loader can
20
- discover the gem-owned `nk--*` controllers. Never copy Nitro components,
21
- helpers, or controllers into the application.
22
- 7. Remove confirmed Nitro Kit 1.x shadows such as application-owned
23
- `NitroKit` components, `nk_*` helpers, or `controllers/nk` only when this
24
- task is authorized to migrate the application.
25
- 8. Use ordinary Rails routes, models, forms, and server-rendered HTML. Follow
26
- the installed Rails and Hotwire guidance for new work. During a migration,
27
- 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
7
+ 2. Choose the project-local Nitro Kit skill matching the task. It will resolve
8
+ and read the installed, version-matched `docs/agent_guide.md`.
9
+ 3. Inspect the application before editing. Preserve established view, asset,
10
+ authentication, and testing conventions unless the task changes them.
11
+ 4. For a greenfield application, run `bin/rails generate phlex:install` and use
12
+ Phlex for the application layout, route views, and reusable UI. In an
13
+ established application, introduce Phlex only at the requested boundary.
14
+ Do not perform an application-wide migration unless it is explicitly
15
+ authorized.
16
+ 5. Verify that the application loads Nitro Kit CSS, the appearance bootstrap,
17
+ Turbo, Stimulus, and the normal Stimulus controller loader. Never copy Nitro
18
+ components or `nk--*` controllers into the application.
19
+ 6. Verify one application base component includes `NitroKit`, and model-backed
20
+ forms select `NitroKit::FormBuilder` explicitly.
21
+ 7. Run `bin/rails nitro_kit:doctor`, fix actionable failures, and run the
32
22
  application's relevant tests.
33
23
 
34
- Report what you changed, any existing convention you deliberately preserved,
35
- any unsupported control recorded as a Nitro Kit coverage gap, and any warning
36
- that still needs a product decision.
24
+ If this is a Nitro Kit 1.x migration, stop and follow
25
+ `docs/migration_1_to_2.md` from the installed gem. Replace a control only when
26
+ 2.x provides a genuine semantic and behavioral equivalent. Otherwise preserve
27
+ it as application-owned Rails and semantic HTML. Never retain copied Nitro Kit
28
+ 1.x source as the fallback.
29
+
30
+ Report changes, preserved conventions, unsupported controls, and unresolved
31
+ decisions.
@@ -1,15 +1,18 @@
1
1
  # Migrating a Nitro Kit 1.x application
2
2
 
3
+ **Audience:** Coding agents and developers migrating an existing application
4
+ from Nitro Kit 1.x to 2.x.
5
+
3
6
  Treat a 1.x migration as a product-flow review, not a helper rename. Nitro Kit
4
7
  2 deliberately removed copied components, `nk_*` helpers, application-owned
5
8
  `controllers/nk`, and unrestricted utility-class customization.
6
9
 
7
- ## Install the 2.0 alpha before migrating
10
+ ## Install the 2.0 prerelease before migrating
8
11
 
9
- Add the 2.0 alpha prerelease to the application's Gemfile:
12
+ Add the 2.0 prerelease to the application's Gemfile:
10
13
 
11
14
  ```ruby
12
- gem "nitro_kit", "2.0.0.alpha.2"
15
+ gem "nitro_kit", "2.0.0.alpha.4"
13
16
  ```
14
17
 
15
18
  Bundler records the exact released version in `Gemfile.lock`; commit `Gemfile`
@@ -22,14 +25,19 @@ rather than a moving Git branch.
22
25
  ## Inventory behavior before editing
23
26
 
24
27
  1. List every `nk_*` helper, `NitroKit::*` component, copied Nitro source file,
25
- and `controllers/nk` controller.
28
+ `controllers/nk` controller, rendered native or Rails button helper, and
29
+ application-owned button class such as `.btn`.
26
30
  2. Group usage by user flow: authentication, settings, collection browsing,
27
31
  mobile navigation, editing, destructive actions, notifications, and
28
32
  copy/share controls.
29
33
  3. Record behavior that must survive: native element, submitted method and
30
34
  parameters, Turbo target, accessible name and description, focus behavior,
31
35
  narrow-screen presentation, empty/error state, and visual density.
32
- 4. Capture representative wide and narrow screenshots before conversion.
36
+ 4. Inventory the existing semantic primary, focus, destructive, neutral, font,
37
+ density, and radius decisions. Translate those roles to public `--nk-*`
38
+ tokens rather than choosing similar raw palette values. Record separately
39
+ when buttons use a distinct shape from inputs and surfaces.
40
+ 5. Capture representative wide and narrow screenshots before conversion.
33
41
 
34
42
  If the Nitro Kit MCP catalog is connected, search it by workflow after this
35
43
  inventory — for example `mobile transcript navigation`, `settings form`, or
@@ -46,11 +54,12 @@ Map each flow to the highest-level matching 2.x component first:
46
54
  | Sign-in or recovery card | `AuthShell` |
47
55
  | Application navigation | `AppShell`, `AppNavigation` |
48
56
  | Mobile contextual navigation or details | `Sheet` |
49
- | Settings screen | `SettingsLayout`, `FormSection` |
57
+ | Settings screen | `SettingsLayout`, `SettingsSection` |
50
58
  | Empty collection card | `EmptyState` |
51
59
  | Data collection | `DataSection`, `Table`, `PaginationBar` |
52
60
  | Destructive settings | `DangerZone`, `Dialog`, `ButtonTo` |
53
61
  | Joined copy or filter controls | `ControlGroup` |
62
+ | Joined action controls | `ButtonGroup` |
54
63
 
55
64
  Only then replace remaining atoms. Common direct mappings include:
56
65
 
@@ -107,7 +116,7 @@ end
107
116
  ```
108
117
 
109
118
  The executable minimal version is on the
110
- [`AppNavigation` gallery page](/gallery/components/app-navigation#example-app-navigation-minimal).
119
+ [`AppNavigation` gallery page](https://gallery.nitrokit.dev/gallery/components/app-navigation#example-app-navigation-minimal).
111
120
 
112
121
  ### Dialog
113
122
 
@@ -158,7 +167,7 @@ end
158
167
  ```
159
168
 
160
169
  The gallery runs this structure at narrow widths in
161
- [`Narrow transcript actions`](/gallery/components/dialog#example-dialog-narrow-action-cluster).
170
+ [`Narrow transcript actions`](https://gallery.nitrokit.dev/gallery/components/dialog#example-dialog-narrow-action-cluster).
162
171
 
163
172
  ### Sheet
164
173
 
@@ -198,7 +207,7 @@ end
198
207
  ```
199
208
 
200
209
  See the executable
201
- [`Sheet` collection example](/gallery/components/sheet#example-sheet-constructions).
210
+ [`Sheet` collection example](https://gallery.nitrokit.dev/gallery/components/sheet#example-sheet-constructions).
202
211
 
203
212
  ### SettingsLayout
204
213
 
@@ -237,13 +246,22 @@ end
237
246
  ```
238
247
 
239
248
  See the executable
240
- [`SettingsLayout` minimal example](/gallery/components/settings-layout#example-settings-layout-cardinality-states).
249
+ [`SettingsLayout` minimal example](https://gallery.nitrokit.dev/gallery/components/settings-layout#example-settings-layout-cardinality-states).
241
250
 
242
251
  ## Preserve unsupported behavior honestly
243
252
 
244
- When no equivalent exists, keep semantic Rails or HTML under the application
245
- namespace and report the missing capability. Do not retain copied 1.x source,
246
- downgrade a specialized control, or hide the gap behind a generic component.
253
+ Replace a form control only when the installed Nitro Kit catalog provides a
254
+ genuine semantic and behavioral equivalent. Preserve its parameter name, IDs,
255
+ values, errors, accessibility, uploads, and browser behavior. If no equivalent
256
+ exists, keep or re-express the control as application-owned Rails and semantic
257
+ HTML, optionally inside a custom `form.field` composition. Never downgrade an
258
+ editor, autocomplete, date range, upload, or other specialized input to the
259
+ nearest generic Nitro control merely for visual consistency.
260
+
261
+ Do not retain copied Nitro Kit 1.x source as the fallback. Remove the legacy
262
+ component, preserve the unsupported behavior in clearly application-owned
263
+ code, and report the missing equivalent as a Nitro Kit coverage gap. Do not
264
+ hide the gap behind a generic component.
247
265
 
248
266
  Tooltip custom triggers are the explicit composition path for an existing
249
267
  focusable control. Forward every yielded boundary to that actual control:
@@ -276,13 +294,19 @@ bin/rails test test/system/nitro_kit_upgrade_smoke_test.rb
276
294
 
277
295
  The generator does not overwrite existing tests. It generates only files
278
296
  supported by the host's Rails Minitest and system-test setup and prints setup
279
- guidance for skipped files. The tests use the currently bundled gem and cover
280
- the shared upgrade boundary browser-submitted Turbo validation and mutation,
281
- 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
285
- product behavior alongside them.
297
+ guidance for skipped files. Keep the generated explanatory header in each
298
+ test: these are host-integration regression checks, not substitutes for
299
+ application-specific acceptance tests. Run every supported generated test
300
+ before and after the application conversion, while retaining
301
+ application-specific acceptance coverage for every inventoried flow.
302
+
303
+ The tests use the currently bundled gem and cover the shared upgrade boundary
304
+ — browser-submitted Turbo validation and mutation, Dialog and Sheet,
305
+ layout-owned Rails flash, Turbo Frame identity, redirects, and post-mutation
306
+ Phlex rendering. Their route is prepended only during each test, so host
307
+ catch-all routes remain compatible; an exact GET or PATCH route at the same
308
+ path is still rejected rather than masked. The route is restored afterward and
309
+ adds no production route or component source.
286
310
 
287
311
  The endpoint deliberately inherits `ApplicationController` callbacks. If the
288
312
  application requires authentication or current-account state, fill in the
@@ -291,20 +315,52 @@ and account-selection helpers used by ordinary integration and system tests.
291
315
  Extend those application-owned classes rather than changing gem test support
292
316
  or skipping host callbacks.
293
317
 
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,
318
+ Run focused request and component tests, then compare the converted flows
319
+ across the dated matrix in `docs/browser_support.md`, including Mobile Safari
320
+ where available, at wide and narrow widths. Exercise keyboard focus, dialogs and sheets,
296
321
  Turbo submissions, errors, empty states, light/dark appearance, and dense
297
- metadata. A green request suite does not prove that a tooltip, off-canvas
322
+ metadata. Inspect computed styles for missing application classes, stacked
323
+ Button content, broken compound corners, double focus rings, clipping, and
324
+ theme drift. A green request suite does not prove that a tooltip, off-canvas
298
325
  panel, interactive Card treatment, or responsive composition survived.
299
326
 
300
327
  Finish by deleting copied components, helpers, and controllers; run
301
328
  `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,
329
+ fallback as either intentional product UI or a Nitro Kit coverage gap. Use
330
+ `bin/rails nitro_kit:doctor --format=json` when migration automation needs
331
+ stable `status`, `label`, and `detail` fields.
332
+
333
+ Before declaring the migration complete, search for every
334
+ `desperately_need_a_class:` use and review each one. Aim for zero. Move layout,
335
+ spacing, width, responsive positioning, and application colors to an
336
+ application-owned wrapper; use documented component options and native
337
+ attributes for semantics and state; accept Nitro defaults where the difference
338
+ is incidental; and keep specialized controls or navigation application-owned
339
+ when Nitro is not the right abstraction. Remove generic class forwarding from
340
+ shared builders. A scoped wrapper rule may target ordinary descendant elements
341
+ when the application truly owns that layout behavior, such as fixed table
342
+ layout. Retain the escape only when a named external integration actually
343
+ requires a class hook, and document why.
344
+
345
+ For such a retained integration, `desperately_need_a_class:` accepts Rails-style
346
+ strings, symbols, nested arrays, or conditional hashes without manual
347
+ `compact.join(" ")` formatting. A Tailwind application may use its own
348
+ Tailwind-aware merger when that hook needs conflicting utility classes
349
+ resolved. Nitro does not require the dependency because its own component CSS
350
+ is static and classless.
351
+
352
+ Doctor inventories concrete Nitro Kit 1.x conventions: `nk_*` helpers,
305
353
  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
354
+ `app/javascript/controllers/nk`, and the old Floating UI and combobox packages.
355
+ When application CSS defines a `.btn` treatment, Doctor
356
+ also records rendered `btn` class usages as application-owned review work; it
357
+ does not assume every specialized control should become a Nitro Button. Doctor
358
+ also uses Ruby syntax trees to catch provable 2.0 runtime violations: direct
359
+ `id:` keywords on Table compound methods and statically icon-only
360
+ `NitroKit::Button`, Dropdown trigger, and Sheet trigger declarations without an
361
+ accessible name. Rendering remains
362
+ the final authority for dynamic wrappers and delegated component declarations.
363
+ Every finding includes a file and replacement or review instruction. Its
308
364
  disposition is:
309
365
 
310
366
  - `migrated` — no remaining occurrence in that category.
@@ -314,5 +370,7 @@ disposition is:
314
370
  an application namespace, not as a Nitro shadow.
315
371
 
316
372
  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.
373
+ or dependency names beyond a button treatment the application itself defines.
374
+ Review application-owned product behavior separately and keep its migration
375
+ record with the application. Re-audit native buttons, Rails button helpers, and
376
+ application-owned button classes after Doctor is otherwise clean.
@@ -1,136 +1,62 @@
1
- # A durable application foundation
1
+ # Application foundation
2
2
 
3
- Start a small authenticated application with the same records and page grammar
4
- it will need after the second person joins. The first-user case should be the
5
- smallest instance of the team model, not a separate personal mode that must be
6
- replaced later.
3
+ **Audience:** Coding agents and developers starting an authenticated,
4
+ team-aware Nitro Kit application.
7
5
 
8
6
  ## Summary
9
7
 
10
- - Model `User`, `Team`, and `Membership` from the first signup; role belongs to
11
- `Membership`, and every team-owned record loads through `Current.team`.
12
- - One hybrid `AppShell` frames the authenticated product: `AppNavigation` owns
13
- brand and destinations, the shell `Toolbar` owns route titles and persistent
14
- basic actions.
15
- - One wrapper immediately inside `shell.main` owns responsive page padding for
16
- every route; the shell owns viewport height and scrolling.
17
- - Infrequent account destinations go after `navigation.spacer`; settings
18
- compose with `SettingsLayout` and plain `FormSection` regions.
19
- - When destination count warrants search, compose one `CommandPalette` in the
20
- shell and render only routes the current membership may visit.
21
- - Centralize cross-cutting feedback in one flash-driven toast region rendered
22
- by the layout.
8
+ - Model `User`, `Team`, and `Membership`; roles belong to memberships, and
9
+ tenant-owned records load through `Current.team`.
10
+ - Use one `AppShell` for the authenticated product and one application-owned
11
+ content gutter inside `shell.main`.
12
+ - Put route titles and persistent actions in the shell `Toolbar`; keep
13
+ destinations in `AppNavigation`.
14
+ - Use links for settings destinations and one layout-level
15
+ `Toast::FlashMessages` region for server feedback.
23
16
 
24
- ## Use memberships from the first user
25
-
26
- Use `User`, `Team`, and `Membership` even when signup creates exactly one of
27
- each. Put the role on `Membership`, not `User`, because authority belongs to a
28
- person's relationship with a team. Create the first team and owner membership
29
- in the same transaction as signup.
17
+ ## Membership and current context
30
18
 
19
+ Create the first team and owner membership in the same transaction as signup.
31
20
  Set `Current.user`, `Current.membership`, and `Current.team` from the session.
32
- Load every team-owned record through `Current.team`; use `Current.user` for
33
- authorship and audit fields. Start with the smallest role vocabulary the
34
- product needs, usually owner, administrator, and member. Protect the last owner
35
- in the domain model rather than only hiding a button.
36
-
37
- Invitations should belong to a team, record the inviter and intended role,
38
- expire, match the invited email, and be consumed when accepted. An existing
39
- user accepts into a new membership; a new user completes signup and then uses
40
- the same acceptance path.
41
-
42
- ## Compose one authenticated frame
43
-
44
- Use one hybrid `AppShell` for the authenticated product. Keep the brand and
45
- primary destinations in `AppNavigation`; place route titles and persistent
46
- basic actions in the shell `Toolbar`. One wrapper immediately inside
47
- `shell.main` owns responsive page padding for every route. The topbar and
48
- sidebar header should use the shell's shared height and border tokens rather
49
- than independent padding guesses.
50
-
51
- Put infrequent account navigation after `navigation.spacer`, near the account
52
- controls at the bottom of the sidebar:
53
-
54
- ```ruby
55
- shell.navigation do
56
- AppNavigation(label: "Workspace navigation") do |navigation|
57
- navigation.body do
58
- navigation.item("Inventory", href: assets_path, icon: :archive)
59
- navigation.item("Team", href: team_path, icon: :users)
60
- navigation.spacer
61
- navigation.item("Settings", href: settings_profile_path, icon: :settings)
62
- end
63
- end
64
- end
65
- ```
66
-
67
- Application code owns destinations and current-route policy. Nitro owns shell
68
- layout, mobile disclosure, focus management, and navigation semantics.
69
- For larger products, place one `CommandPalette` in the shell. Its native links
70
- remain the navigation authority while Command-K or Control-K adds fast
71
- filtering. Render the same authorized destination set the user can reach in
72
- ordinary navigation; do not use the palette to bypass route policy.
73
- When the destination set is too large or dynamic to render eagerly, pass
74
- `search_url:` and return `CommandPalette::Results` from that endpoint. Keep the
75
- same authorization scope on the initial links and every remote query.
76
- Let the shell own viewport height and scrolling; do not add `min-height: 100vh`
77
- to its main region or page wrapper. Put brand and destination icons through the
78
- navigation slots so they share the same left alignment.
79
-
80
- Team is an administration surface, not merely a roster. Include pending
81
- invitations and the complete invite, role-change, removal, and revoke paths,
82
- with last-owner protection in the model and authorization on every mutation.
83
-
84
- Authentication is a standalone form surface. Inside `AuthShell`, use Rails
85
- `form_with` with `NitroKit::FormBuilder` and put the visible fields, submit
86
- control, and related recovery link in one `form.group`. The group owns their
87
- vertical rhythm; `AuthShell` owns only the page container and spacing between
88
- its major regions.
21
+ Use `Current.user` for authorship and audit fields; scope tenant data through
22
+ the team. Protect the last owner in the model.
89
23
 
90
- ## Keep settings plain
24
+ Invitations belong to a team, record inviter and role, expire, and match the
25
+ invited email. Existing and new users should share one acceptance path.
91
26
 
92
- Use `SettingsLayout` inside the normal shell main region. Its navigation lists
93
- stable subsections such as Profile, Notifications, Appearance, and Password;
94
- its content renders the selected form. Use `FormSection` for genuinely distinct
95
- form regions and ordinary whitespace or dividers between them.
27
+ ## Authenticated shell
96
28
 
97
- Render subsection destinations as links and mark the active link with
98
- `aria-current="page"`. They navigate between routes; Buttons and ButtonGroup
99
- would incorrectly present them as in-page actions. Small preferences may
100
- submit on change through a tiny application Stimulus controller that calls the
101
- form's native `requestSubmit`. Keep a submit control in `noscript` so the form
102
- still works without JavaScript.
29
+ Use one `AppShell`, normally `layout: :hybrid`, for authenticated routes.
30
+ `AppNavigation` owns brand and destinations; a `Toolbar` in `shell.topbar`
31
+ owns the route's single `h1` and persistent actions. One wrapper inside
32
+ `shell.main` owns responsive page padding. Do not add another viewport-height
33
+ or outer-padding rule in child pages.
103
34
 
104
- The route still has one `h1` in the shell toolbar. Do not repeat “Settings” in
105
- the page body, wrap each subsection in a Card, or give every form its own outer
106
- padding. A toolbar Save button can submit the selected form with the native
107
- `form:` attribute, so the action stays in the same place at narrow and wide
108
- widths without JavaScript.
35
+ Application code owns destinations, authorization, and current-route policy.
36
+ Nitro owns responsive disclosure and focus behavior. Put infrequent account
37
+ destinations after `navigation.spacer`. Add one `CommandPalette` only when the
38
+ destination count warrants search, and render only authorized routes.
109
39
 
110
- ## Centralize cross-cutting feedback
40
+ Use `AuthShell` with Rails `form_with` and `NitroKit::FormBuilder` for
41
+ authentication. Put visible fields, submit, and recovery link in one
42
+ `form.group`.
111
43
 
112
- Render `Toast::FlashMessages` once in the application layout. Keep using
113
- ordinary Rails flash and `303 See Other` redirects from controllers.
44
+ ## Settings and feedback
114
45
 
115
- Compact destructive actions should continue to declare
116
- `data: { turbo_confirm: "" }` and use Turbo's native browser confirmation.
117
- When the user needs branded review UI or more context than one sentence,
118
- compose a dedicated native Nitro `Dialog` at the action's call site. The
119
- browser's top layer keeps that inline dialog clear of ancestor clipping and
120
- stacking contexts. Put record deletion on the edit route rather than adding a
121
- danger surface to every operational show page.
46
+ Render `SettingsLayout` inside the normal shell. Settings destinations are
47
+ links with `aria-current="page"`, not Buttons. Use `SettingsSection` only for
48
+ distinct form regions; do not wrap every subsection in a Card or repeat the
49
+ route title.
122
50
 
123
- ## Baseline acceptance path
51
+ Render `NitroKit::Toast::FlashMessages` once in the application layout. Keep
52
+ ordinary Rails flash and `303 See Other` redirects. Use the dedicated
53
+ [destructive action](destructive_action.md) and
54
+ [flash](flash_and_toast.md) patterns for those flows.
124
55
 
125
- Before polishing empty-state illustration or dashboard summaries, verify:
56
+ ## Acceptance checklist
126
57
 
127
- - signup or sign-in selects the current membership and team;
128
- - another team cannot load the current team's records;
129
- - owner, administrator, and member policy differs where intended;
130
- - populated, empty, invalid, narrow, and destructive states work;
131
- - settings forms preserve validation and use one content gutter;
132
- - successful mutations redirect with `303` and invalid forms render with
133
- `422`;
134
- - confirmation has both cancel and confirm coverage;
135
- - the shell, headings, tables, and forms remain usable without custom request
136
- JavaScript.
58
+ - Signup or sign-in selects a membership and team.
59
+ - Cross-team records cannot be loaded.
60
+ - Owner, administrator, and member policy differs where intended.
61
+ - Populated, empty, invalid, narrow, settings, and destructive states work.
62
+ - Successful mutations redirect with `303`; invalid forms render with `422`.