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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +179 -0
  3. data/README.md +22 -26
  4. data/STYLE_GUIDE.md +10 -0
  5. data/app/assets/stylesheets/nitro_kit-tailwind-v4.css +3 -3
  6. data/app/assets/stylesheets/nitro_kit.css +1220 -572
  7. data/app/components/nitro_kit/accordion.rb +1 -19
  8. data/app/components/nitro_kit/alert.rb +1 -11
  9. data/app/components/nitro_kit/app_navigation.rb +44 -9
  10. data/app/components/nitro_kit/appearance_picker.rb +10 -1
  11. data/app/components/nitro_kit/badge.rb +13 -4
  12. data/app/components/nitro_kit/combobox.rb +1 -17
  13. data/app/components/nitro_kit/command_palette.rb +1 -0
  14. data/app/components/nitro_kit/dropzone.rb +1 -1
  15. data/app/components/nitro_kit/form_builder.rb +1 -1
  16. data/app/components/nitro_kit/select.rb +1 -14
  17. data/app/components/nitro_kit/tabs.rb +1 -0
  18. data/app/javascript/controllers/nk/command_palette_controller.js +4 -0
  19. data/app/javascript/controllers/nk/tabs_controller.js +13 -1
  20. data/docs/agent_guide.md +63 -138
  21. data/docs/agent_native_spec.md +72 -400
  22. data/docs/browser_support.md +77 -188
  23. data/docs/component_contracts.md +56 -36
  24. data/docs/customization.md +675 -89
  25. data/docs/hotwire.md +61 -99
  26. data/docs/initialization_prompt.md +27 -57
  27. data/docs/migration_1_to_2.md +34 -18
  28. data/docs/patterns/application_foundation.md +45 -121
  29. data/docs/patterns/crud_resource.md +44 -129
  30. data/docs/patterns/destructive_action.md +45 -102
  31. data/docs/patterns/flash_and_toast.md +18 -36
  32. data/docs/patterns/inline_edit.md +21 -44
  33. data/docs/patterns/queryable_collection.md +65 -161
  34. data/docs/patterns/resource_form.md +33 -81
  35. data/docs/rails_conventions.md +38 -78
  36. data/docs/rails_integration.md +96 -474
  37. data/lib/nitro_kit/installation.rb +11 -3
  38. data/lib/nitro_kit/migration_inventory.rb +15 -8
  39. data/lib/nitro_kit/version.rb +1 -1
  40. data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +7 -0
  41. data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +15 -8
  42. data/src/stylesheets/nitro_kit/components/accordion.css +4 -4
  43. data/src/stylesheets/nitro_kit/components/alert.css +20 -29
  44. data/src/stylesheets/nitro_kit/components/app_navigation.css +74 -2
  45. data/src/stylesheets/nitro_kit/components/app_shell.css +3 -3
  46. data/src/stylesheets/nitro_kit/components/appearance_picker.css +20 -8
  47. data/src/stylesheets/nitro_kit/components/auth_shell.css +2 -2
  48. data/src/stylesheets/nitro_kit/components/avatar.css +12 -8
  49. data/src/stylesheets/nitro_kit/components/avatar_stack.css +12 -10
  50. data/src/stylesheets/nitro_kit/components/badge.css +13 -9
  51. data/src/stylesheets/nitro_kit/components/button.css +42 -25
  52. data/src/stylesheets/nitro_kit/components/card.css +2 -2
  53. data/src/stylesheets/nitro_kit/components/checkbox.css +29 -46
  54. data/src/stylesheets/nitro_kit/components/checkbox_group.css +3 -3
  55. data/src/stylesheets/nitro_kit/components/combobox.css +6 -6
  56. data/src/stylesheets/nitro_kit/components/command_palette.css +62 -4
  57. data/src/stylesheets/nitro_kit/components/container.css +6 -6
  58. data/src/stylesheets/nitro_kit/components/control_group.css +9 -9
  59. data/src/stylesheets/nitro_kit/components/danger_zone.css +6 -6
  60. data/src/stylesheets/nitro_kit/components/data_section.css +4 -4
  61. data/src/stylesheets/nitro_kit/components/details_table.css +7 -5
  62. data/src/stylesheets/nitro_kit/components/dialog.css +3 -3
  63. data/src/stylesheets/nitro_kit/components/dropdown.css +7 -7
  64. data/src/stylesheets/nitro_kit/components/dropzone.css +7 -7
  65. data/src/stylesheets/nitro_kit/components/empty_state.css +4 -4
  66. data/src/stylesheets/nitro_kit/components/field.css +2 -2
  67. data/src/stylesheets/nitro_kit/components/fieldset.css +2 -2
  68. data/src/stylesheets/nitro_kit/components/flex.css +1 -1
  69. data/src/stylesheets/nitro_kit/components/grid.css +1 -1
  70. data/src/stylesheets/nitro_kit/components/icon.css +5 -5
  71. data/src/stylesheets/nitro_kit/components/input.css +33 -6
  72. data/src/stylesheets/nitro_kit/components/page_header.css +4 -4
  73. data/src/stylesheets/nitro_kit/components/pagination.css +3 -3
  74. data/src/stylesheets/nitro_kit/components/pagination_bar.css +2 -2
  75. data/src/stylesheets/nitro_kit/components/palette.css +140 -72
  76. data/src/stylesheets/nitro_kit/components/progressive_image.css +1 -1
  77. data/src/stylesheets/nitro_kit/components/radio_button.css +28 -33
  78. data/src/stylesheets/nitro_kit/components/radio_button_group.css +6 -6
  79. data/src/stylesheets/nitro_kit/components/rich_text_area.css +2 -2
  80. data/src/stylesheets/nitro_kit/components/select.css +24 -8
  81. data/src/stylesheets/nitro_kit/components/settings_layout.css +5 -5
  82. data/src/stylesheets/nitro_kit/components/settings_section.css +2 -2
  83. data/src/stylesheets/nitro_kit/components/sheet.css +10 -10
  84. data/src/stylesheets/nitro_kit/components/stat_grid.css +1 -1
  85. data/src/stylesheets/nitro_kit/components/switch.css +34 -40
  86. data/src/stylesheets/nitro_kit/components/table.css +8 -4
  87. data/src/stylesheets/nitro_kit/components/tabs.css +5 -1
  88. data/src/stylesheets/nitro_kit/components/textarea.css +20 -5
  89. data/src/stylesheets/nitro_kit/components/toast.css +11 -58
  90. data/src/stylesheets/nitro_kit/components/toolbar.css +2 -2
  91. data/src/stylesheets/nitro_kit/components/tooltip.css +4 -0
  92. data/src/stylesheets/nitro_kit/components/typeset.css +4 -4
  93. data/src/stylesheets/nitro_kit/reset.css +6 -6
  94. data/src/stylesheets/nitro_kit/tokens.css +497 -74
  95. metadata +1 -2
  96. data/docs/new_app_strategy.md +0 -22
data/docs/hotwire.md CHANGED
@@ -1,106 +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.
5
+
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.
6
9
 
7
10
  ## Choose the smallest interaction
8
11
 
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. For browser-sensitive interactions, follow
20
- [`browser_support.md`](browser_support.md); Nitro owns compatibility bridges for
21
- its own components.
22
-
23
- ## Response matrix
24
-
25
- | Request | Success | Invalid or denied |
26
- | -------------------- | --------------------------------------------- | ------------------------------------------------------------------- |
27
- | HTML form mutation | Redirect with `303 See Other` | Render HTML with `422` |
28
- | Frame form mutation | Redirect or render the same frame ID | Render the same frame ID with `422` |
29
- | Stream form mutation | Return only when multiple targets must change | Render the invalid form target with `422` and keep an HTML fallback |
30
- | GET query | Render from URL parameters | Render a useful empty or error state |
31
-
32
- Use `dom_id` or one named constant for a frame. The show, edit, invalid,
33
- success, and cancel responses must preserve that identifier.
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
-
39
- ## Forms and application Stimulus
40
-
41
- Let Turbo submit real Rails forms. Use `data-turbo-submits-with` to opt a Button
42
- into submission feedback without replacing its visible label. The Button dims
43
- immediately; `submission_indicator: :spinner` additionally reveals an indicator
44
- after 1 second and lets the Button expand to fit it. Use `data-turbo-confirm` for
45
- compact destructive confirmation.
46
- Reviewed destructive flows may compose `DangerZone` and `Dialog`, but the
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).
52
-
53
- Keep application controllers small, declarative, and disposable. For a
54
- self-submitting control, the complete controller can be:
55
-
56
- ```js
57
- import { Controller } from "@hotwired/stimulus";
58
-
59
- export default class extends Controller {
60
- submit() {
61
- this.element.requestSubmit();
62
- }
63
- }
64
- ```
65
-
66
- Attach `data-action="change->auto-submit#submit"` to the form so change events
67
- bubble to that one controller root. Keep a submit button inside `noscript` as
68
- the HTML fallback. Use `data-action` instead of manually registering DOM listeners. If a
69
- controller owns a listener, observer, timer, object URL, or third-party
70
- instance, release it in `disconnect`.
71
-
72
- ## Morphing and cache lifecycle
73
-
74
- Default to morphable server-rendered HTML. Use `data-turbo-permanent` only for
75
- a genuinely stateful island, always with a stable unique `id`. Update content
76
- inside a permanent element deliberately instead of making broad page regions
77
- permanent.
78
-
79
- Clean ephemeral UI before Turbo caches the page. Nitro Kit controllers own
80
- their own cache and reconnect behavior; application controllers must do the
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.
84
-
85
- ## Frame escape and recovery
86
-
87
- An authentication redirect or error response inside a frame must not strand
88
- the user behind a missing-frame error. Use `_top` when navigation must leave
89
- the frame. Keep authentication and authorization handling capable of returning
90
- a full-page response, and handle `turbo:frame-missing` only when the
91
- application has a deliberate recovery policy.
92
-
93
- Give lazy frames meaningful loading content. A failed frame should leave an
94
- understandable state and a path to retry or continue without JavaScript.
95
-
96
- ## Verify behavior
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.
18
+
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.
22
+
23
+ ## Response contract
24
+
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 |
31
+
32
+ Authentication and authorization failures are separate policy decisions; do
33
+ not return `422` for them.
34
+
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.
39
+
40
+ ## Stimulus and lifecycle
41
+
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).
46
+
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`.
50
+
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.
54
+
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.
58
+
59
+ ## Verify
97
60
 
98
61
  - Request-test `303`, `422`, HTML fallback, and stable frame IDs.
99
- - On mutation responses, assert submitted content inside the response's
100
- matching frame, not merely somewhere in the response body.
101
- - System-test focus, dialogs, frame navigation, and multi-target changes.
102
- - Use Capybara assertions that wait for the DOM; never use `sleep`.
103
- - Test navigation, morphing, and reconnection without duplicating controller
104
- roots or listeners.
105
-
106
- 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,61 +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. 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
15
- conventions unless they conflict with the requested Nitro Kit 2 setup.
16
- 4. Ensure an application base Phlex component includes `NitroKit` once and
17
- product components inherit from it.
18
- 5. Re-run `bin/rails generate nitro_kit:install`. Ensure its application layout
19
- setup has one appearance bootstrap before every stylesheet, then optional
20
- third-party base styles, the optional Tailwind adapter, `nitro_kit`, compiled
21
- Tailwind, and application token overrides in that order. Add flash toast
22
- rendering when the application uses it.
23
- 6. Ensure Turbo and Stimulus are wired and the normal Stimulus loader can
24
- discover the gem-owned `nk--*` controllers. Never copy Nitro components,
25
- helpers, or controllers into the application.
26
- 7. Remove confirmed Nitro Kit 1.x shadows such as application-owned
27
- `NitroKit` components, `nk_*` helpers, or `controllers/nk` only when this
28
- task is authorized to migrate the application.
29
- 8. Use ordinary Rails routes, models, forms, and server-rendered HTML. Follow
30
- the installed Rails and Hotwire guidance for new work. During a migration,
31
- replace an existing control only when Nitro Kit 2 has a genuine semantic
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.
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
22
+ application's relevant tests.
58
23
 
59
- Report what you changed, any existing convention you deliberately preserved,
60
- any unsupported control recorded as a Nitro Kit coverage gap, and any warning
61
- 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,5 +1,8 @@
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.
@@ -9,7 +12,7 @@ Treat a 1.x migration as a product-flow review, not a helper rename. Nitro Kit
9
12
  Add the 2.0 prerelease to the application's Gemfile:
10
13
 
11
14
  ```ruby
12
- gem "nitro_kit", "2.0.0.alpha.3"
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`
@@ -30,7 +33,7 @@ rather than a moving Git branch.
30
33
  3. Record behavior that must survive: native element, submitted method and
31
34
  parameters, Turbo target, accessible name and description, focus behavior,
32
35
  narrow-screen presentation, empty/error state, and visual density.
33
- 4. Inventory the existing semantic primary, focus, danger, neutral, font,
36
+ 4. Inventory the existing semantic primary, focus, destructive, neutral, font,
34
37
  density, and radius decisions. Translate those roles to public `--nk-*`
35
38
  tokens rather than choosing similar raw palette values. Record separately
36
39
  when buttons use a distinct shape from inputs and surfaces.
@@ -113,7 +116,7 @@ end
113
116
  ```
114
117
 
115
118
  The executable minimal version is on the
116
- [`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).
117
120
 
118
121
  ### Dialog
119
122
 
@@ -164,7 +167,7 @@ end
164
167
  ```
165
168
 
166
169
  The gallery runs this structure at narrow widths in
167
- [`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).
168
171
 
169
172
  ### Sheet
170
173
 
@@ -204,7 +207,7 @@ end
204
207
  ```
205
208
 
206
209
  See the executable
207
- [`Sheet` collection example](/gallery/components/sheet#example-sheet-constructions).
210
+ [`Sheet` collection example](https://gallery.nitrokit.dev/gallery/components/sheet#example-sheet-constructions).
208
211
 
209
212
  ### SettingsLayout
210
213
 
@@ -243,13 +246,22 @@ end
243
246
  ```
244
247
 
245
248
  See the executable
246
- [`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).
247
250
 
248
251
  ## Preserve unsupported behavior honestly
249
252
 
250
- When no equivalent exists, keep semantic Rails or HTML under the application
251
- namespace and report the missing capability. Do not retain copied 1.x source,
252
- 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.
253
265
 
254
266
  Tooltip custom triggers are the explicit composition path for an existing
255
267
  focusable control. Forward every yielded boundary to that actual control:
@@ -282,15 +294,19 @@ bin/rails test test/system/nitro_kit_upgrade_smoke_test.rb
282
294
 
283
295
  The generator does not overwrite existing tests. It generates only files
284
296
  supported by the host's Rails Minitest and system-test setup and prints setup
285
- guidance for skipped files. The tests use the currently bundled gem and cover
286
- the shared upgrade boundary browser-submitted Turbo validation and mutation,
287
- Dialog and Sheet, layout-owned Rails flash, Turbo Frame identity, redirects,
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
293
- 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.
294
310
 
295
311
  The endpoint deliberately inherits `ApplicationController` callbacks. If the
296
312
  application requires authentication or current-account state, fill in the
@@ -1,138 +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 `SettingsSection` 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, and set the document language on the root `html` element.
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. 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.
52
-
53
- Put infrequent account navigation after `navigation.spacer`, near the account
54
- controls at the bottom of the sidebar:
55
-
56
- ```ruby
57
- shell.navigation do
58
- AppNavigation(label: "Workspace navigation") do |navigation|
59
- navigation.body do
60
- navigation.item("Inventory", href: assets_path, icon: :archive)
61
- navigation.item("Team", href: team_path, icon: :users)
62
- navigation.spacer
63
- navigation.item("Settings", href: settings_profile_path, icon: :settings)
64
- end
65
- end
66
- end
67
- ```
68
-
69
- Application code owns destinations and current-route policy. Nitro owns shell
70
- layout, mobile disclosure, focus management, and navigation semantics.
71
- For larger products, place one `CommandPalette` in the shell. Its native links
72
- remain the navigation authority while Command-K or Control-K adds fast
73
- filtering. Render the same authorized destination set the user can reach in
74
- ordinary navigation; do not use the palette to bypass route policy.
75
- When the destination set is too large or dynamic to render eagerly, pass
76
- `search_url:` and return `CommandPalette::Results` from that endpoint. Keep the
77
- same authorization scope on the initial links and every remote query.
78
- Let the shell own viewport height and scrolling; do not add `min-height: 100vh`
79
- to its main region or page wrapper. Put brand and destination icons through the
80
- navigation slots so they share the same left alignment.
81
-
82
- Team is an administration surface, not merely a roster. Include pending
83
- invitations and the complete invite, role-change, removal, and revoke paths,
84
- with last-owner protection in the model and authorization on every mutation.
85
-
86
- Authentication is a standalone form surface. Inside `AuthShell`, use Rails
87
- `form_with` with `NitroKit::FormBuilder` and put the visible fields, submit
88
- control, and related recovery link in one `form.group`. The group owns their
89
- vertical rhythm; `AuthShell` owns only the page container and spacing between
90
- 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.
91
23
 
92
- ## 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.
93
26
 
94
- Use `SettingsLayout` inside the normal shell main region. Its navigation lists
95
- stable subsections such as Profile, Notifications, Appearance, and Password;
96
- its content renders the selected form. Use `SettingsSection` for genuinely distinct
97
- form regions and ordinary whitespace or dividers between them.
27
+ ## Authenticated shell
98
28
 
99
- Render subsection destinations as links and mark the active link with
100
- `aria-current="page"`. They navigate between routes; Buttons and ButtonGroup
101
- would incorrectly present them as in-page actions. Small preferences may
102
- submit on change through a tiny application Stimulus controller that calls the
103
- form's native `requestSubmit`. Keep a submit control in `noscript` so the form
104
- 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.
105
34
 
106
- The route still has one `h1` in the shell toolbar. Do not repeat “Settings” in
107
- the page body, wrap each subsection in a Card, or give every form its own outer
108
- padding. A toolbar Save button can submit the selected form with the native
109
- `form:` attribute, so the action stays in the same place at narrow and wide
110
- 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.
111
39
 
112
- ## 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`.
113
43
 
114
- Render `Toast::FlashMessages` once in the application layout. Keep using
115
- ordinary Rails flash and `303 See Other` redirects from controllers.
44
+ ## Settings and feedback
116
45
 
117
- Compact destructive actions should continue to declare
118
- `data: { turbo_confirm: "" }` and use Turbo's native browser confirmation.
119
- When the user needs branded review UI or more context than one sentence,
120
- compose a dedicated native Nitro `Dialog` at the action's call site. The
121
- browser's top layer keeps that inline dialog clear of ancestor clipping and
122
- stacking contexts. Put record deletion on the edit route rather than adding a
123
- 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.
124
50
 
125
- ## 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.
126
55
 
127
- Before polishing empty-state illustration or dashboard summaries, verify:
56
+ ## Acceptance checklist
128
57
 
129
- - signup or sign-in selects the current membership and team;
130
- - another team cannot load the current team's records;
131
- - owner, administrator, and member policy differs where intended;
132
- - populated, empty, invalid, narrow, and destructive states work;
133
- - settings forms preserve validation and use one content gutter;
134
- - successful mutations redirect with `303` and invalid forms render with
135
- `422`;
136
- - confirmation has both cancel and confirm coverage;
137
- - the shell, headings, tables, and forms remain usable without custom request
138
- 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`.