rails_admin_next 1.1.2 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2bdbd212982bdb74e9a2288a5c14eba746b74b0a0ed902afbb0671cbdfd5c107
4
- data.tar.gz: 119040f95b74f79a05571d9f099cb98668efa5921ee2a4b43668b3fb320302ec
3
+ metadata.gz: 957743cf70d6fa24bb0cb1eaf74ca15b4a3b2996e0dca4f5dcee2d6428985d6b
4
+ data.tar.gz: a205770a7170c2b1ce7e04a85cd6422f6cda1d150da237df81795387af8cc18d
5
5
  SHA512:
6
- metadata.gz: 583c910d3d95569fb8031b30bfda1a71ae61ecc090bdd0e8289d9e0d4ea01ee7cf635e417c912d17a2a22c5d9f1bfe764d057391fb58744c21c1c22099130531
7
- data.tar.gz: f7c7784f080f80977c21d9ae3bc02a822a1b2576aafe07f0dc063c4c7702180f425ea7905f00d8171e0a889a8842311cbff67ffd8268601eee68659b8bfdd032
6
+ metadata.gz: ad848e8080f75c1e3d6b12c180dcb745bb4a3caea7791ee08d4fec1d97567836effacf01ea340a764b0febb4a52c345e793b3d1708d7496e9d1c2e3f6a167f6d
7
+ data.tar.gz: 37ec4a516104eb2ecc4d8924c758ad862b8fa091f32a0d460b0dfda2f8bc566a233fa61ea81d6cdbbfe49695b1d71eb0aed55f75c281001e9d86a73821fbe8a8
data/Gemfile CHANGED
@@ -19,6 +19,10 @@ group :test do
19
19
  gem "generator_spec"
20
20
  gem "image_processing"
21
21
  gem "pundit"
22
+ # Pinned within 0.10: Gemfile.lock is not committed here, so CI resolves fresh on every run
23
+ # and no Dependabot PR stages a release first, and the discovery spec drives Hyperdrive's
24
+ # pre-1.0 discovery API, which has renamed keys within 0.x before.
25
+ gem "rails-hyperdrive", "~> 0.10.1", require: false
22
26
  gem "rspec-rails"
23
27
  gem "rspec-retry"
24
28
  gem "ruby-vips"
data/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  [![Build](https://github.com/6temes/rails_admin_next/actions/workflows/test.yml/badge.svg)](https://github.com/6temes/rails_admin_next/actions/workflows/test.yml)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE.md)
11
- [![Ruby](https://img.shields.io/badge/Ruby-4.0.5%2B-CC342D.svg)](https://www.ruby-lang.org)
11
+ [![Ruby](https://img.shields.io/badge/Ruby-4.0.9%2B-CC342D.svg)](https://www.ruby-lang.org)
12
12
  [![Rails](https://img.shields.io/badge/Rails-8.1-CC0000.svg)](https://rubyonrails.org)
13
13
 
14
14
  [Why RailsAdminNext?](#why-railsadminnext) ◆ [Quick Start](#quick-start) ◆ [Installation](#installation) ◆ [Architecture](#architecture) ◆ [Security](#security) ◆ [Upgrading](#upgrading-from-railsadmin)
@@ -32,7 +32,7 @@ multi-bundler, jQuery-era machinery. RailsAdminNext keeps the admin and drops th
32
32
  - **⚡ Zero build step** — browser-native ESM over **importmap + Propshaft**; no Webpack/Vite/Sprockets, no `yarn build`, no generated pins.
33
33
  - **🧩 Turbo + Stimulus** — the jQuery / jQuery-UI / Bootstrap-JS / `@rails/ujs` stack is gone, replaced by engine-shipped Stimulus controllers; styling is hand-maintained vanilla CSS.
34
34
  - **🔒 Hardened & CDN-free** — a documented CSRF / clickjacking / mass-assignment / CSP posture ([SECURITY.md](SECURITY.md)) and **zero external CDN**, default or opt-in — every asset is self-hosted off a gem.
35
- - **🎯 One modern target** — Ruby ≥ 4.0.5, Rails ~> 8.1, ActiveRecord only, latest Chrome + Safari. No Mongoid, no multi-bundler / multi-Rails matrix, no legacy-browser CSS.
35
+ - **🎯 One modern target** — Ruby ≥ 4.0.9, Rails ~> 8.1, ActiveRecord only, latest Chrome + Safari. No Mongoid, no multi-bundler / multi-Rails matrix, no legacy-browser CSS.
36
36
  - **↩️ Familiar** — `RailsAdminNext.config`, per-model `rails_admin_next do … end` blocks, and CanCanCan / Pundit authorization all work as before; upgrading from RailsAdmin is a mechanical rename.
37
37
 
38
38
  ## Quick Start
@@ -50,17 +50,21 @@ bin/rails s # then open http://localhost:3000/admin
50
50
 
51
51
  That's the whole setup — no `yarn add`, no asset build, no generated pins. The engine ships its own importmap and CSS.
52
52
 
53
- > **Prerequisites:** Ruby ≥ 4.0.5 · Rails ~> 8.1 · ActiveRecord · a [Propshaft][propshaft] + [importmap-rails][importmap] host (both are pulled in as dependencies). See [Installation](#installation) for the full walkthrough.
53
+ > **Prerequisites:** Ruby ≥ 4.0.9 · Rails ~> 8.1 · ActiveRecord · a [Propshaft][propshaft] + [importmap-rails][importmap] host (both are pulled in as dependencies). See [Installation](#installation) for the full walkthrough.
54
54
 
55
55
  ## Requirements
56
56
 
57
- - Ruby >= 4.0.5
57
+ - Ruby >= 4.0.9
58
58
  - Rails ~> 8.1
59
59
  - ActiveRecord (Mongoid is not supported)
60
60
  - [Propshaft][propshaft] and [importmap-rails][importmap] (both are pulled in as
61
61
  dependencies; RailsAdminNext serves its frontend over them, with no bundler or build step)
62
62
  - Latest stable Chrome or Safari for the admin's users (see [Browser support](#browser-support))
63
63
 
64
+ RailsAdminNext expects **evergreen** hosts. The Ruby floor follows the latest stable Ruby
65
+ patch release, and a release can raise it without a major version bump. Keep your app on
66
+ current Ruby and Rails to keep receiving updates.
67
+
64
68
  [propshaft]: https://github.com/rails/propshaft
65
69
  [importmap]: https://github.com/rails/importmap-rails
66
70
 
@@ -83,6 +87,22 @@ The install generator mounts the engine, writes
83
87
  `config/initializers/rails_admin_next.rb`, and does nothing else — the engine ships its
84
88
  own importmap and CSS, so there is no `yarn add`, no asset build, and no generated pins.
85
89
 
90
+ ### Agent skill
91
+
92
+ RailsAdminNext is a [Rails Hyperdrive][hyperdrive] companion gem. It ships a
93
+ `rails-admin-next` skill that teaches coding agents this fork's API rather than upstream
94
+ RailsAdmin's. In an app running Hyperdrive, sync installs the skill, and each later
95
+ release updates it:
96
+
97
+ ```bash
98
+ bin/rails hyperdrive:sync
99
+ ```
100
+
101
+ The skill points agents at the `docs/*.md` files packaged in the gem, so its references
102
+ always match the installed version.
103
+
104
+ [hyperdrive]: https://github.com/rails-hyperdrive/rails-hyperdrive
105
+
86
106
  ## Features
87
107
 
88
108
  - CRUD any data with ease
data/Rakefile CHANGED
@@ -8,6 +8,8 @@ Dir["lib/tasks/*.rake"].each { |rake| load rake }
8
8
  require "bundler"
9
9
  Bundler::GemHelper.install_tasks
10
10
 
11
+ require "hyperdrive/skill_tasks"
12
+
11
13
  require "rspec/core/rake_task"
12
14
  RSpec::Core::RakeTask.new(:spec)
13
15
 
@@ -108,6 +108,17 @@ module RailsAdminNext
108
108
  model_config.send(action).with(controller: self, view: view_context, object: @object).visible_fields
109
109
  end
110
110
 
111
+ # Memoized, as export renders through index, which switches @action
112
+ def serialization_schema
113
+ @serialization_schema ||= begin
114
+ section = %i[export show].include?(@action.key) ? @model_config.send(@action.key) : @model_config.list
115
+ SerializationSchema.new(
116
+ section.with(controller: self, view: view_context, object: @object || @abstract_model.model.new),
117
+ include_associations: @action.key == :export
118
+ )
119
+ end
120
+ end
121
+
111
122
  def sanitize_params_for!(action, model_config = @model_config, target_params = params[@abstract_model.param_key], nested_in = nil)
112
123
  return unless target_params.present?
113
124
 
data/docs/UPGRADING.md ADDED
@@ -0,0 +1,495 @@
1
+ # Upgrading from rails_admin to rails_admin_next
2
+
3
+ `rails_admin_next` is the modernized fork of `rails_admin`: Rails 8.1 / Ruby 4.0 floor,
4
+ ActiveRecord only, an importmap + Propshaft asset pipeline, a Stimulus + Turbo
5
+ frontend (no jQuery, jQuery-UI, Bootstrap JS, or `@rails/ujs`), vanilla CSS with a
6
+ latest-Chrome/Safari browser floor, and a hardened security posture. This guide covers
7
+ the one-time changes a host app needs.
8
+
9
+ The Ruby namespace is renamed **`RailsAdmin` → `RailsAdminNext`** (and `rails_admin` →
10
+ `rails_admin_next`), so your initializer, engine mount, model macros, and route helpers
11
+ need a mechanical rename. The **authorization contract is deliberately unchanged** — the
12
+ `:access, :rails_admin` subject and the `admin.*` I18n namespace stay exactly as they were,
13
+ so your CanCanCan abilities, Pundit policies, and translations need no edits.
14
+
15
+ ## 1. Gemfile
16
+
17
+ Replace the upstream `rails_admin` gem with the fork (distributed via `git:`):
18
+
19
+ ```ruby
20
+ # Gemfile
21
+ gem 'rails_admin_next', git: 'https://github.com/6temes/rails_admin_next'
22
+ ```
23
+
24
+ Remove the old `gem 'rails_admin'` line, then `bundle install`.
25
+
26
+ ## 2. Application code — rename the namespace
27
+
28
+ Rename `RailsAdmin` → `RailsAdminNext` and `rails_admin` → `rails_admin_next` in your
29
+ initializer, mount, per-model blocks, and route-helper calls:
30
+
31
+ ```ruby
32
+ # config/initializers/rails_admin_next.rb (renamed from config/initializers/rails_admin.rb)
33
+ RailsAdminNext.config do |config|
34
+ config.model User do
35
+ # …
36
+ end
37
+
38
+ # DELETE this line if your old initializer had it — the option was removed
39
+ # (there is a single importmap + Propshaft pipeline now; leaving it in place
40
+ # only logs a deprecation warning):
41
+ # config.asset_source = :importmap
42
+ end
43
+
44
+ # config/routes.rb
45
+ mount RailsAdminNext::Engine => '/admin', as: 'rails_admin_next'
46
+
47
+ # per-model blocks
48
+ class Team < ApplicationRecord
49
+ rails_admin_next do
50
+ field :name
51
+ end
52
+ end
53
+
54
+ # route helpers — these also hide in mailers and regular views
55
+ # (`rails_admin_url(...)` → `rails_admin_next_url(...)`) and inside field blocks
56
+ # (`bindings[:view].rails_admin.show_path(...)` → `bindings[:view].rails_admin_next.show_path(...)`)
57
+ rails_admin_next.dashboard_path
58
+ rails_admin_next.edit_path(model_name: 'team', id: team.id)
59
+ ```
60
+
61
+ **Keep the mount alias `rails_admin_next`.** The engine's own views resolve routes through
62
+ the `rails_admin_next` route proxy, so the mount must expose that helper name — either
63
+ `as: 'rails_admin_next'` as above, or no `as:` at all (it defaults to the engine name).
64
+ Aliasing the mount to something else to preserve old helper names (e.g. `as: 'rails_admin'`)
65
+ breaks every engine page at render time with
66
+ `undefined local variable or method 'rails_admin_next'`.
67
+
68
+ Find every host-side call site that needs the rename with:
69
+
70
+ ```bash
71
+ grep -rnE 'rails_admin_url|rails_admin_path|\.rails_admin\.' app config
72
+ ```
73
+
74
+ Running `rails g rails_admin_next:install` writes the new initializer and mount for you.
75
+
76
+ ## 3. Icons (`link_icon` / `navigation_icon`)
77
+
78
+ Font Awesome is **no longer shipped** (no icon font, no `font-awesome.css`). Icons now
79
+ render as inline SVG, and the icon options take a **logical icon name** (a Symbol or
80
+ String) instead of a Font Awesome class string:
81
+
82
+ ```ruby
83
+ # Before
84
+ RailsAdminNext.config do |config|
85
+ config.model Team do
86
+ navigation_icon 'fas fa-trophy'
87
+ end
88
+ config.actions do
89
+ # …
90
+ my_custom_action { link_icon 'fas fa-rocket' }
91
+ end
92
+ end
93
+
94
+ # After
95
+ RailsAdminNext.config do |config|
96
+ config.model Team do
97
+ navigation_icon :list # a logical name (see the list below)
98
+ end
99
+ config.actions do
100
+ my_custom_action { link_icon :show_in_app }
101
+ end
102
+ end
103
+ ```
104
+
105
+ Available logical names (see `RailsAdminNext::Icons::GLYPHS`):
106
+
107
+ `:dashboard` (alias `:home`), `:list`, `:show` (alias `:info`), `:new` (alias `:add`/`:plus`),
108
+ `:edit` (alias `:pencil`), `:delete` (aliases `:cancel`/`:times`), `:export`,
109
+ `:history` (alias `:book`), `:show_in_app` (alias `:eye`), `:bulk_delete` (alias `:question`),
110
+ `:collapse`, `:expand`, `:calendar`, `:check`, `:minus`, `:refresh` (alias `:sync`),
111
+ `:trash`, `:move_right`, `:move_left`, `:move_up`, `:move_down`.
112
+
113
+ **Legacy Font Awesome strings still given to `link_icon`/`navigation_icon` are handled
114
+ gracefully**: the glyph is best-effort mapped to the nearest logical name and a deprecation
115
+ warning is emitted (`RailsAdminNext.deprecator`); a value that can't be mapped renders no
116
+ icon rather than raising. Migrate to logical names to silence the warning. If you relied on
117
+ Font Awesome classes elsewhere in your own admin views, vendor Font Awesome yourself — the
118
+ engine no longer provides it.
119
+
120
+ ## Authorization (no change needed)
121
+
122
+ The authorization contract is held stable — **do not touch your abilities or policies**:
123
+
124
+ - The access subject is `:rails_admin`.
125
+ - Every action's authorization key (`:dashboard`, `:index`, `:show`, `:new`, `:edit`,
126
+ `:export`, `:destroy`, `:history`, `:show_in_app`) is unchanged.
127
+
128
+ ```ruby
129
+ # This CanCanCan ability keeps working unchanged:
130
+ can :access, :rails_admin
131
+ ```
132
+
133
+ The access subject is deliberately frozen and locked by a spec in the gem — a renamed
134
+ subject can fail *open* (a `cannot` rule that no longer matches silently grants access).
135
+
136
+ ## Asset prerequisites
137
+
138
+ RailsAdminNext ships its frontend over **importmap + Propshaft** only — there is no
139
+ build step and no Sprockets/Webpacker/Webpack/Vite support. The host app must use:
140
+
141
+ - **Propshaft** (`config.assets.*` via `propshaft`), and
142
+ - **importmap-rails**.
143
+
144
+ Optional:
145
+
146
+ - **Thruster** in front of the app for edge caching of the fingerprinted assets.
147
+ - Set `config.assets.integrity_hash_algorithm` if you want Subresource Integrity on the
148
+ engine's self-hosted modules (off by default; same-origin hosting makes SRI low-value).
149
+
150
+ ## Browser support
151
+
152
+ The frontend targets the **latest stable Chrome and Safari only**. This fork is an internal
153
+ tool for teams that control their admin users' browsers; the old "Baseline 2023 /
154
+ Firefox ≥ 111" contract is retired deliberately, and no legacy-browser fallbacks ship. The
155
+ engine's CSS and JS assume `:has()`, `<dialog>`, popovers, CSS nesting, `oklch()`,
156
+ `color-mix()`, `light-dark()`, `@starting-style`, and CSS anchor positioning.
157
+
158
+ Two features are newer still: `closedby="any"` (backdrop light-dismissal of the inline-edit
159
+ dialog) needs Chromium 134+ / Safari 26, and CSS anchor positioning needs Chromium 128+ /
160
+ Safari 26 (dropdown menus are positioned by anchor CSS alone — no JS fallback ships).
161
+ `field-sizing: content` (auto-growing textareas) is a Chromium-only enhancement — other
162
+ browsers keep fixed-height textareas.
163
+
164
+ ## Styling: cascade layers and design tokens
165
+
166
+ Bootstrap's SCSS is gone. Styling is hand-maintained vanilla CSS delivered through three
167
+ cascade layers, declared in `rails_admin.css` (the layer names are a documented contract):
168
+
169
+ - `ra.tokens` — design tokens (CSS custom properties)
170
+ - `ra.framework` — the trimmed ex-Bootstrap component CSS
171
+ - `ra.skin` — the engine's own widgets, icons, and admin skin
172
+
173
+ Because all engine CSS is layered, **any unlayered host rule beats it** — a plain rule in
174
+ your own stylesheet overrides the admin without `!important` or specificity games:
175
+
176
+ ```css
177
+ /* host stylesheet — no layer, so it wins over everything the engine ships */
178
+ .badge {
179
+ border-radius: 7px;
180
+ }
181
+ ```
182
+
183
+ ### Supported override tokens (public API)
184
+
185
+ Hosts may override these tokens (unlayered, e.g. `:root { --ra-primary: #663399; }`) and
186
+ every derived color — button hover/active states, focus rings, alert tints, table tints,
187
+ links — follows automatically:
188
+
189
+ | Token | Role |
190
+ | --- | --- |
191
+ | `--ra-primary`, `--ra-secondary`, `--ra-success`, `--ra-info`, `--ra-warning`, `--ra-danger`, `--ra-light`, `--ra-dark` | brand and status colors |
192
+ | `--ra-body-bg`, `--ra-body-color` | body surface and text |
193
+ | `--ra-link-color` | link color (defaults to a tint of `--ra-primary`) |
194
+
195
+ A single-color override applies to both light and dark schemes; hosts that want a tuned dark
196
+ mode should override with their own pair:
197
+
198
+ ```css
199
+ :root {
200
+ --ra-primary: light-dark(#663399, #a97fd6);
201
+ }
202
+ ```
203
+
204
+ **Every other `--ra-*` name is internal and may change without notice.**
205
+
206
+ ### Dark mode and the `color_scheme` knob
207
+
208
+ The admin now follows the OS color-scheme preference automatically: every color token is a
209
+ `light-dark()` pair, resolved against a `<meta name="color-scheme">` the layout renders from
210
+ the new config knob:
211
+
212
+ ```ruby
213
+ RailsAdminNext.config do |config|
214
+ config.color_scheme = :auto # default; :light or :dark pins the scheme
215
+ end
216
+ ```
217
+
218
+ Pin `:light` to keep the old always-light rendering. Two deliberate exceptions: the navbar is
219
+ scheme-locked (`navbar_css_classes` express host intent — `navbar-dark bg-primary` stays a
220
+ dark navbar under both schemes), and the Trix (ActionText) editor stays light-only for now.
221
+
222
+ ### Token mapping: `--bs-*` → `--ra-*`
223
+
224
+ The Bootstrap `--bs-*` custom properties are gone. Renamed with the same meaning — read or
225
+ override the new name:
226
+
227
+ | Old | New | Note |
228
+ | --- | --- | --- |
229
+ | `--bs-body-bg` | `--ra-body-bg` | |
230
+ | `--bs-body-color` | `--ra-body-color` | |
231
+ | `--bs-body-font-family` | `--ra-body-font-family` | |
232
+ | `--bs-body-font-size` | `--ra-body-font-size` | |
233
+ | `--bs-body-font-weight` | `--ra-body-font-weight` | |
234
+ | `--bs-body-line-height` | `--ra-body-line-height` | |
235
+ | `--bs-body-text-align` | `--ra-body-text-align` | still undeclared by default; set it to opt in, exactly like Bootstrap's knob |
236
+ | `--bs-breadcrumb-divider` | `--ra-breadcrumb-divider` | |
237
+ | `--bs-font-monospace` | `--ra-font-monospace` | |
238
+ | `--bs-font-sans-serif` | `--ra-font-sans-serif` | |
239
+ | `--bs-gradient` | `--ra-gradient` | |
240
+ | `--bs-gutter-x` / `--bs-gutter-y` | `--ra-gutter-x` / `--ra-gutter-y` | grid-scoped |
241
+ | `--bs-table-bg` | `--ra-table-bg` | table-scoped; likewise `-accent-bg`, `-striped-bg`, `-striped-color`, `-active-bg`, `-active-color`, `-hover-bg`, `-hover-color` |
242
+
243
+ Renamed with a value-space change (number → percentage):
244
+
245
+ | Old | New |
246
+ | --- | --- |
247
+ | `--bs-bg-opacity: 0.5` | `--ra-bg-opacity: 50%` |
248
+ | `--bs-text-opacity: 0.5` | `--ra-text-opacity: 50%` |
249
+
250
+ Previously declared but consumed by nothing — now live semantic tokens (overriding the
251
+ `--bs-*` name never did anything; the `--ra-*` name works): `--bs-primary` →
252
+ `--ra-primary`, `--bs-secondary` → `--ra-secondary`, `--bs-success` → `--ra-success`,
253
+ `--bs-info` → `--ra-info`, `--bs-warning` → `--ra-warning`, `--bs-danger` → `--ra-danger`,
254
+ `--bs-light` → `--ra-light`, `--bs-dark` → `--ra-dark`.
255
+
256
+ Removed — replaced by derivation (never override these; override the base token instead):
257
+ the whole `-rgb` family (`--bs-primary-rgb` … `--bs-dark-rgb`, `--bs-white-rgb`,
258
+ `--bs-black-rgb`, `--bs-body-color-rgb`, `--bs-body-bg-rgb`) is gone; utilities now derive
259
+ translucency via `color-mix(in srgb, var(--ra-*) <pct>, transparent)`.
260
+
261
+ Removed without replacement (declared but never consumed): the raw color ladder
262
+ (`--bs-blue`, `--bs-indigo`, `--bs-purple`, `--bs-pink`, `--bs-red`, `--bs-orange`,
263
+ `--bs-yellow`, `--bs-green`, `--bs-teal`, `--bs-cyan` — the code-pink role of `--bs-pink`
264
+ lives on as the internal `--ra-code-color`), `--bs-white`, `--bs-gray`, `--bs-gray-dark`,
265
+ `--bs-gray-100` … `--bs-gray-900` (the grays the CSS actually uses exist as internal
266
+ `--ra-gray-*` tokens), and `--bs-position` (set by `.dropdown-menu-end` for Bootstrap's
267
+ dropdown JS, which this engine does not ship).
268
+
269
+ ### Removed component CSS
270
+
271
+ The flattened Bootstrap CSS was trimmed to what the engine actually renders. These component
272
+ families were deleted as unreachable:
273
+
274
+ | Removed family | If you relied on it |
275
+ | --- | --- |
276
+ | Carousel | No engine view renders one; ship your own carousel CSS/JS in custom actions. |
277
+ | Tooltip (`.tooltip`, `.bs-tooltip-*`) | Bootstrap tooltips needed Bootstrap JS, which the engine does not ship; use the native `title` attribute or your own library. |
278
+ | Popover component (`.popover`, `.bs-popover-*`) | Same as tooltips. (The native `popover` *attribute* is what the engine now uses for its dropdown menus.) |
279
+ | Toasts | Engine flash messages render as alerts; `alert-*` stays supported. |
280
+ | Offcanvas | Never emitted by engine views. |
281
+ | Accordion | Sidebar groups are native `<details>`/`<summary>` now. |
282
+ | Spinners (`.spinner-border`, `.spinner-grow`) | The engine's loading indicator is the `#loading` badge. |
283
+ | Placeholders (loading skeletons) | Never emitted by engine views. |
284
+ | Floating labels (`.form-floating`) | Engine forms use stacked labels. |
285
+ | `.form-range` | No engine form renders a range input. |
286
+ | Form validation (`.is-valid` / `.is-invalid` / `*-feedback` / `*-tooltip`) | Engine forms render server-side errors through their own `.error` + `.help-inline.text-danger` markup, which is kept. |
287
+ | `.form-check` / `.form-switch` wrapper styling | Checkboxes/radios are native, tinted via `accent-color`; `.form-check-input` itself is still styled. |
288
+ | jQuery-UI chrome | jQuery-UI is gone. |
289
+ | wysihtml5 editor chrome | The editor was dropped; use ActionText/Trix. |
290
+ | `.collapsing` | Bootstrap JS's collapse-transition class; sidebar collapse is a native `<details>`. |
291
+ | `[data-bs-popper]` rules | Popper.js is not shipped. |
292
+ | `.dropup` / `.dropend` / `.dropstart` | Engine menus only drop down (and flip above the toggle automatically near the viewport edge). |
293
+ | Responsive tiers `sm` / `lg` / `xl` / `xxl` | The `.col-sm-*` grid columns engine forms emit survive; the responsive display/spacing/text utilities and `container-*` tiers are gone (a couple of `md` utilities remain). |
294
+ | Print utilities (`.d-print-*`) | Gone; add your own print CSS if you print admin pages. |
295
+
296
+ ### The styled vocabulary (keep-list)
297
+
298
+ The stylesheet keeps a deliberate keep-list even where no engine view currently emits the
299
+ class: vocabularies that are host-facing configuration API remain styled and supported —
300
+
301
+ - `.navbar-light` / `.navbar-dark` — the `navbar_css_classes` value space,
302
+ - `alert-*` — the `flash_alert_class` value space,
303
+ - `bg-*` utilities — badges, indicators, and the host navbar,
304
+ - `.badge` colors,
305
+ - `table-*` contextual rows — `row_css_class` conventions.
306
+
307
+ Class names the stylesheet does not style are out of contract and may stay unstyled.
308
+
309
+ ## Markup changes
310
+
311
+ Three widgets moved from Bootstrap markup to native platform primitives. Hand-rolled host CSS
312
+ or JS that targeted the old DOM needs updating — `.modal.fade`, `.dropdown .show`, and the
313
+ sidebar's `.collapse` no longer appear:
314
+
315
+ | Widget | Was | Now |
316
+ | --- | --- | --- |
317
+ | Inline-edit modal | `.modal.fade` + `.modal-dialog` divs | native `<dialog id="modal" closedby="any">` |
318
+ | Dropdown menus (filters, bulk actions) | `<a role="button">` toggles + `.dropdown .show` | `<button popovertarget>` toggles + `popover="auto"` menus, positioned with CSS anchor positioning |
319
+ | Sidebar navigation groups | `.collapse` + Bootstrap collapse classes | native `<details>`/`<summary>` disclosures |
320
+
321
+ ## Required `belongs_to` associations
322
+
323
+ A `belongs_to` your model actually requires is now rendered as required. Previously only a
324
+ presence or numericality validator on the association or its foreign key was detected, and
325
+ Rails registers the validator it adds for a required `belongs_to` with an `if:` condition that
326
+ the detection skipped.
327
+
328
+ `belongs_to_required_by_default` has been on since `load_defaults 5.0`. On `load_defaults 7.1`
329
+ or later Rails also sets `belongs_to_required_validates_foreign_key = false`, which is what makes
330
+ the presence validator conditional and invisible to the old detection — so this affects every
331
+ plain `belongs_to` whose foreign key carries no validator of its own. For those fields:
332
+
333
+ - the help text reads "Required." instead of "Optional."
334
+ - a plain association select carries `required`, so the browser blocks submission while it is
335
+ empty; polymorphic association fields change help text and filter operators only, because
336
+ their partial does not apply the field's HTML attributes
337
+ - the association select no longer offers its clear-selection entry
338
+ - the index filter loses the "is present" and "is blank" operators
339
+
340
+ An association declared with `default:` is left optional, since Rails fills it in a
341
+ `before_validation` callback. If a model fills the association server-side some other way, the
342
+ browser will now block a form the server would have accepted. Mark the field optional:
343
+
344
+ ```ruby
345
+ RailsAdminNext.config Order do
346
+ edit do
347
+ field :customer do
348
+ optional true
349
+ end
350
+ end
351
+ end
352
+ ```
353
+
354
+ ## Nested forms hide the link back to the parent
355
+
356
+ A nested subform no longer renders the child's association back to the record being edited.
357
+ Editing a `FieldTest` with `has_one :comment, as: :commentable` used to ask, from inside that
358
+ form, which record the comment belonged to — a choice the parent had already made and that the
359
+ nested save overwrote anyway.
360
+
361
+ This widened along with the association's notion of an inverse. `inverse_of` now answers with
362
+ the declared `inverse_of:`, otherwise the inverse ActiveRecord resolves for the reflection,
363
+ otherwise a polymorphic `as:`. It previously answered only the declared option, so a pair that
364
+ declared nothing on either side — the conventional case Rails resolves on its own — reported no
365
+ inverse and the back-reference stayed on screen. Combined with required `belongs_to` detection
366
+ above, that field could be marked required while being one only the nested save can fill.
367
+
368
+ Two consequences worth checking in a host app:
369
+
370
+ - A subform for a pair that relies on Rails' automatic inverse detection now hides one more
371
+ field than before, and its key is no longer permitted inside `<assoc>_attributes[...]` either.
372
+ Only at nested depth: editing the child on its own still permits its own association, which is
373
+ a legitimate way to set it.
374
+ - `field.inverse_of` in your own configuration blocks now returns a symbol for associations
375
+ that previously returned `nil`.
376
+
377
+ **Narrowing the allowlist is a security fix, for collections.** Saving a singular association re-applies
378
+ the owner to the child, so a submitted back-reference never survived there. A collection does not:
379
+ `assign_nested_attributes_for_collection_association` assigns straight onto an already-associated
380
+ record, so a crafted back-reference inside `<assoc>_attributes[n]` moved that child onto a
381
+ different parent — one the submitter may have had no rights over. Dropping the key from the
382
+ allowlist closes it, because it never reaches the assignment.
383
+
384
+ `inverse_of: false` on the association is honoured, as in Rails. To keep the back-reference
385
+ visible — and permitted — for one field without touching the model, disable the parent field's
386
+ inverse. On a collection that re-opens the re-parenting described above, so scope it to the one
387
+ field that needs it:
388
+
389
+ ```ruby
390
+ RailsAdminNext.config FieldTest do
391
+ edit do
392
+ field :comment do
393
+ inverse_of { false }
394
+ end
395
+ end
396
+ end
397
+ ```
398
+
399
+ Note the block: a bare `inverse_of nil` reads the option rather than setting it.
400
+
401
+ ## The show action answers HTML by default
402
+
403
+ `show` used to declare its JSON format before its HTML one, so a request carrying no
404
+ `Accept` header — `curl`, a bare `fetch()`, most HTTP client libraries — arrived as
405
+ `*/*`, matched every registered type, and got JSON back. Every other action answered
406
+ HTML for the same request.
407
+
408
+ It now declares HTML first. A client that explicitly sends `Accept: application/json`,
409
+ or requests the `.json` extension, is unaffected.
410
+
411
+ If you have a script relying on the unheadered JSON response, send the header or use
412
+ `.json`:
413
+
414
+ ```bash
415
+ curl -H 'Accept: application/json' https://example.com/admin/player/1
416
+ curl https://example.com/admin/player/1.json
417
+ ```
418
+
419
+ The JSON branch renders only the fields the show view displays (see below).
420
+
421
+ ## JSON and XML serialize only configured fields
422
+
423
+ The index action's `.json`/`.xml`, the show action's `.json`, and the export action's JSON/XML
424
+ used to serialize every attribute of the record, and export passed the submitted `schema` straight
425
+ to `to_json`/`to_xml` — so any `methods` or `include` name in it was called on every record.
426
+
427
+ They now serialize only what the matching section shows: `list` for index, `show` for show,
428
+ `export` for export. Index and show also keep the foreign keys of the `belongs_to` associations
429
+ they display. A submitted export `schema` is intersected with the export fields, and `except` is
430
+ ignored.
431
+
432
+ If a client relied on a column the section doesn't show, add that field to the section.
433
+
434
+ ## Pundit: a model with no policy Scope lists nothing
435
+
436
+ A model whose policy defines no `Scope` used to list every record. It now lists none: the
437
+ list, export, bulk delete and dashboard count all return an empty set. Bulk delete deletes
438
+ whatever the scope returns, with no per-record check, so "no Scope" can't mean "everything".
439
+
440
+ Define a `Scope` for each model the admin exposes:
441
+
442
+ ```ruby
443
+ class PlayerPolicy < ApplicationPolicy
444
+ class Scope < ApplicationPolicy::Scope
445
+ def resolve
446
+ scope.all
447
+ end
448
+ end
449
+ end
450
+ ```
451
+
452
+ CanCanCan hosts are unaffected.
453
+ ## Removed: options deprecated before RailsAdmin 3.0
454
+
455
+ These six were deprecated upstream between 2019-07 and 2022-01 and had no effect for
456
+ years. They are gone rather than carried into this gem's 1.0:
457
+
458
+ | Removed | What to do |
459
+ | --- | --- |
460
+ | `config.total_columns_width=` | Nothing. It had no effect. |
461
+ | `config.sidescroll=` | Nothing. Horizontal scroll with frozen columns is always on. |
462
+ | `list.sidescroll` | Nothing. Same as above. The field-level `sticky?` still controls which columns freeze. |
463
+ | `list.sort_reverse` | Nothing. It had no effect. The field-level `sort_reverse?` is unrelated and unaffected. |
464
+ | `momentjs_format` | Nothing. Date/time fields are native HTML5 inputs and render per the browser locale. |
465
+ | `eager_load?` | Rename to `eager_load`. |
466
+ | `delete_key` (`:multiple_file_upload`) | Rename to `delete_value`. |
467
+
468
+ `register_deprecated_instance_option` itself stays — extensions and host apps use it for
469
+ their own options.
470
+
471
+ ## Dropped support
472
+
473
+ These are intentionally gone — migrate off them before upgrading:
474
+
475
+ - **Mongoid** — RailsAdminNext is ActiveRecord only.
476
+ - **Sprockets / Webpacker / Webpack / Vite** — importmap + Propshaft only.
477
+ - **Standalone WYSIWYG/code editor field types** (`bootstrap-wysihtml5`, `froala`, and the
478
+ cdnjs-backed `:ck_editor` / `:code_mirror` / `:simple_mde`) — they loaded JS/CSS from cdnjs.
479
+ Migrate those fields to `:action_text` (self-hosted Trix rich text) or a plain `:text` textarea
480
+ instead. The engine now has **zero external CDN** usage, default and opt-in.
481
+ - **Non-ActiveStorage attachment libraries** (CarrierWave, Paperclip, Shrine, Dragonfly,
482
+ Refile) — RailsAdminNext detects **ActiveStorage** (and ActionText/Trix) attachments only.
483
+ Migrate file/image columns to ActiveStorage before upgrading. Image variants are processed
484
+ with **libvips** via `image_processing` + `ruby-vips` (set
485
+ `config.active_storage.variant_processor = :vips`); install the libvips system library
486
+ (`brew install vips` / `apt-get install libvips-dev`) — ImageMagick is no longer required.
487
+ - **Configurable app branding (`main_app_name`)** — the navbar brand and page `<title>` are
488
+ hardcoded to "Rails AdminNext"; the config option is gone. Fork
489
+ `app/views/layouts/rails_admin_next/_navigation.html.erb` and `content.html.erb` in your
490
+ host app if you need custom branding.
491
+
492
+ ## History / audit table
493
+
494
+ The gem owns **no** history table. Auditing remains host/PaperTrail territory, so there is
495
+ no gem migration to run — your existing `rails_admin_histories` table (if any) is unaffected.
data/docs/security.md ADDED
@@ -0,0 +1,72 @@
1
+ # Security posture
2
+
3
+ The admin is a privileged surface. This engine ships the request-forgery, framing, and
4
+ destructive-action controls itself; a few things are left for the host to opt into.
5
+
6
+ ## Always on
7
+
8
+ - **CSRF.** `protect_from_forgery(with: :exception)` is hardcoded. A non-GET request without a
9
+ valid token is rejected (`ActionController::InvalidAuthenticityToken` → HTTP 422). There is no
10
+ host-configurable forgery option to weaken it.
11
+ - **Clickjacking.** Every admin response carries `X-Frame-Options: SAMEORIGIN`.
12
+ - **Export.** The CSV/JSON/XML data stream requires a non-GET, CSRF-protected request; `GET` only
13
+ renders the export form, so the table can't be exfiltrated via a crafted link.
14
+ - **Serialization.** JSON and XML output is limited to the fields the section shows (`list`,
15
+ `show` or `export`). A submitted export `schema` is intersected with the export fields, so it
16
+ can't name a hidden column, an association the export doesn't show, or a method to call.
17
+ - **Pundit scopes fail closed.** A model whose policy has no `Scope` lists no records, rather
18
+ than all of them.
19
+ - **Destructive actions.** `delete` and `bulk_delete` render a confirmation page and additionally
20
+ carry `data-turbo-confirm` on the destructive submit (Turbo's confirmation, not `@rails/ujs`).
21
+ - **Mass assignment.** Submitted attributes are sliced to the model config's `visible_fields`
22
+ before `permit!`, at the top level and at every nested depth, regardless of the submitted
23
+ nested-attributes cardinality (hash or array). At nested depth the slice also drops the child's
24
+ link back to the record being edited — the field the subform does not draw — so a crafted
25
+ back-reference cannot move a nested record onto a different parent.
26
+
27
+ ## Content Security Policy (opt-in)
28
+
29
+ The engine enforces **no** CSP by default. When you opt in, it applies the policy per-request to
30
+ admin responses only (never to the host app) and threads a per-request nonce onto the tags it
31
+ emits — the importmap JSON, the import entry point and its module preloads, the index
32
+ column-width `<style>`, and the engine's stylesheet and style-preload links — so a `:self` +
33
+ nonce policy does not block the admin's own assets.
34
+
35
+ ```ruby
36
+ # config/initializers/rails_admin_next.rb
37
+ RailsAdminNext.config do |config|
38
+ config.content_security_policy do |policy|
39
+ policy.default_src :self
40
+ policy.script_src :self
41
+ policy.style_src :self, :unsafe_inline # see note below
42
+ policy.img_src :self, :data
43
+ policy.font_src :self
44
+ policy.connect_src :self
45
+ end
46
+ end
47
+ ```
48
+
49
+ Tune it without breaking pages first by reporting only:
50
+
51
+ ```ruby
52
+ RailsAdminNext.config do |config|
53
+ config.content_security_policy(report_only: true) do |policy|
54
+ policy.default_src :self
55
+ end
56
+ end
57
+ ```
58
+
59
+ > **Note on `style-src`.** The admin still renders five inline `style="…"` attributes, which a CSP
60
+ > nonce cannot cover (nonces apply to `<script>`/`<style>` elements, not style attributes), so a
61
+ > `style-src` directive needs `:unsafe_inline` for now. The engine's own `<style>` element (the
62
+ > index column-width block) is nonced. The remaining attributes:
63
+ >
64
+ > - the `#loading` badge's positioning in the layout, and the delete-notice `<li>` spacing —
65
+ > static values, removable by moving them into the stylesheet;
66
+ > - the file-upload delete toggle's initial `display:none` — conditional initial state, removable
67
+ > by switching to the `hidden` attribute;
68
+ > - the dashboard's `.progress` margin (static) and each progress bar's `width: N%` (a per-row
69
+ > dynamic value — the one that genuinely resists removal; it would need the nonced-`<style>`
70
+ > route or a native `<progress>` element).
71
+ >
72
+ > Once those five are reauthored, `:unsafe_inline` can be dropped.
@@ -0,0 +1,145 @@
1
+ # Upstream parity
2
+
3
+ Assessed through upstream `2dfd05f1` (2026-10-30). Fork point: `d8e0809e` (2025-09-14).
4
+
5
+ Divergence from the fork point: 792 files, +21,203 / −73,569 lines.
6
+
7
+ ## How this fork tracks upstream
8
+
9
+ RailsAdminNext is a hard fork of [railsadminteam/rails_admin](https://github.com/railsadminteam/rails_admin). It is **never merged or rebased from upstream**, and it cannot be: the fork deleted whole subsystems upstream still ships — the Mongoid adapter, the npm/webpack build, jQuery and `@rails/ujs`, Bootstrap's JavaScript, Appraisal, CarrierWave. Replaying this fork's history onto a moving upstream would conflict with every commit touching any of them, by construction.
10
+
11
+ Instead, upstream work is absorbed by **reimplementation**: read the upstream commit, judge whether the underlying problem exists here, and write the fix in this codebase's terms. A port therefore need not — and often should not — match upstream's diff. Deliberate divergences are recorded below so they are not "corrected" back later.
12
+
13
+ This means `git rev-list --count upstream/master...master` is not a progress measure. It counts commits never merged, so it does not fall when work is absorbed and it grows every time upstream moves. **This ledger is the state.** The watermark above records what has been assessed; the table records the verdict for each commit, including the ones deliberately skipped.
14
+
15
+ ## Verdicts
16
+
17
+ Commits are listed newest first, as `git log` reports them.
18
+
19
+ | upstream | subject | verdict | ours |
20
+ |---|---|---|---|
21
+ | `2dfd05f1` | Test ferrum 0.19.2 with flatten mode on JRuby | n/a — JRuby leg over Appraisal gemfiles; neither exists here | — |
22
+ | `bf8f645c` | Replace nested_form with RailsAdmin's own nested form implementation | partly relevant — per-controller `<template>` and button controls already here; unsaved-row remove, self-reference recursion, placeholder collision and the dummy belongs_to still apply | [#73](https://github.com/6temes/rails_admin_next/issues/73) |
23
+ | `6e27f4d3` | Let the adapter narrow a scope, instead of merging it here | n/a — one AR adapter; the controller's merge is already its vocabulary, no behaviour change | — |
24
+ | `20f8f29a` | Narrow a listing by the scope it selects before it is paginated | partly adapted — pre-pagination done; export and bulk delete ignore the tab, and remote pickers get it | [#60](https://github.com/6temes/rails_admin_next/issues/60) |
25
+ | `7e5a7d67` | Keep the browser driver off a version the suite hangs on | partly relevant — the pin is JRuby-only (n/a); the missing job timeout matches this fork's apt-step hangs | [#74](https://github.com/6temes/rails_admin_next/issues/74) |
26
+ | `1e0bd4fd` | Say what an upload needs from an embedded document | n/a — embedded documents are Mongoid; no upload docs here | — |
27
+ | `6651f64d` | Say which records associated_collection_scope governs | n/a — the option isn't documented here, so nothing to correct | — |
28
+ | `51d7321a` | Look up a registered visible block by the option's own name | relevant — measured: redeclaring a field drops its visible block; `admin?` raises NameError | [#64](https://github.com/6temes/rails_admin_next/issues/64) |
29
+ | `e67edd00` | Escape a field name before it stands in for a selector | n/a — the filter container is found by `getElementById`; other selectors use `CSS.escape` or quoted values | — |
30
+ | `11cd3e53` | Leave out a column the queried term could never fit | relevant — measured: PG raises NumericValueOutOfRange for a 10-digit term on int4 | [#56](https://github.com/6temes/rails_admin_next/issues/56) |
31
+ | `2d9f951f` | Have no enumeration rather than crash when there's no _enum method | relevant — measured: enum without a source crashes filter_operators; native PG enums hit it unconfigured | [#58](https://github.com/6temes/rails_admin_next/issues/58) |
32
+ | `c4b64ede` | Document sort_by's array and hash forms | n/a — sort_by isn't documented here; the forms are supported | — |
33
+ | `cbed4c42` | Fall back to the guessed encoding when a CSV export leaves it blank | n/a — measured: blank does what the form says here (leaves encoding untouched); the fallback is a no-op on the connection encoding | — |
34
+ | `8748af05` | Bind the controller and view before reading a model's list scopes | relevant — measured: both reads are unbound | [#62](https://github.com/6temes/rails_admin_next/issues/62) |
35
+ | `3e26f685` | Keep a serialized value's wrapper class out of the text an admin edits | relevant — measured: every AR `store` round-trip raises DisallowedClass | [#63](https://github.com/6temes/rails_admin_next/issues/63) |
36
+ | `8b7f686b` | Let a requested sort win over has_paper_trail's own version order | relevant — `order` appended after paper_trail's association order | [#69](https://github.com/6temes/rails_admin_next/issues/69) |
37
+ | `e9e13ebf` | Recognize a native timestamptz column as a timestamp field | relevant — measured: Rails 8.1.4 PG reports `:timestamptz`, which the registry rejects | [#55](https://github.com/6temes/rails_admin_next/issues/55) |
38
+ | `0c6b8e9d` | Version 4.0.0.beta2 | n/a — upstream release chore | — |
39
+ | `d96845da` | [Security] Fail closed when Pundit policy scope is undefined | relevant — the adapter rescued a missing Scope with `model.all`; fixed with plain AR `none` (no repository indirection needed) | [#77](https://github.com/6temes/rails_admin_next/pull/77) |
40
+ | `a6cf6bb0` | [Security] Limit JSON/XML serialization to the configured fields | partly adapted — the GET gate was already here; methods/hidden columns via `schema` and full-row index/show output fixed; the default never calls virtual fields | [#77](https://github.com/6temes/rails_admin_next/pull/77) |
41
+ | `fca081f2` | Version 4.0.0.beta | n/a — upstream release chore | — |
42
+ | `00ddcd06` | Drop the dummy app's tooling for asset modes that no longer exist | n/a — none of those files are tracked here | — |
43
+ | `d593bcaf` | Serve the dummy app's own JavaScript through every asset pipeline | n/a — one pipeline; the host entry is an ESM module importing Turbo | — |
44
+ | `72bdd18d` | Let any callable serve as asset_source | n/a — `asset_source=` is a deprecated no-op here | — |
45
+ | `812109cf` | Put the :external stylesheet where a JS build will not eat it | n/a — the installer writes only an initializer | — |
46
+ | `9ed33f69` | Keep the dummy app's test hooks out of development | n/a — the only test hook here is invisible | — |
47
+ | `7cc5e292` | Keep the dummy app's cookies readable, and cover CSRF both ways | partly relevant — the json pin is moot on Rails 8.1.4; the CSRF acceptance coverage gap applies | [#76](https://github.com/6temes/rails_admin_next/issues/76) |
48
+ | `fd0bf316` | Drop the docs left behind by the asset rework | n/a — docs here mention those pipelines only as removed | — |
49
+ | `3af6946c` | Flush rich-text editors before the modal submits | n/a — Trix writes its input on every change; no submit-time editor remains | — |
50
+ | `a16b425f` | Test carrierwave 3.x and 4.x within the existing CI matrix | n/a — CarrierWave removed | — |
51
+ | `8302cc29` | Lower a search term with String#downcase | adapted — already `downcase`; no `mb_chars` left | — |
52
+ | `63ebb9ac` | Ask whether an enum is null, not whether it equals '' | relevant — measured: PG rejects `= ''` on an integer enum | [#57](https://github.com/6temes/rails_admin_next/issues/57) |
53
+ | `749cbe75` | Let associated_collection_scope decide the order it asks for | relevant — scope applied before the reorder; the swap must follow #60 | [#61](https://github.com/6temes/rails_admin_next/issues/61) |
54
+ | `47da8e99` | Refresh SimpleMDE editors when their tab is shown | n/a — no layout-measuring editor ships here | — |
55
+ | `4443ab35` | Ship a prebuilt asset bundle; collapse config.asset_source | n/a — already a single zero-build importmap + Propshaft path | — |
56
+ | `478484fb` | CI: install ImageMagick against a refreshed package list | n/a — the step here already runs `apt-get update` and `-y`; the failure here is a stall (#74) | — |
57
+ | `a6f4c5ad` | Fix the viewport meta tag's name attribute | relevant — the identical malformed tag is in `_head.html.erb` | [#72](https://github.com/6temes/rails_admin_next/issues/72) |
58
+ | `88e6df4d` | Strip a leading :: when deriving a model's config registry key | relevant — measured: config ignored and the model listed twice | [#70](https://github.com/6temes/rails_admin_next/issues/70) |
59
+ | `a5607f24` | Use label_plural in bulk delete flash messages | relevant — measured: translated plural ignored | [#71](https://github.com/6temes/rails_admin_next/issues/71) |
60
+ | `7b60193b` | Let the page catch up before reading the database | adapted — fixed independently here | [#23](https://github.com/6temes/rails_admin_next/pull/23) |
61
+ | `ef3e3f57` | Keep one dead browser from failing the examples after it | partly relevant — the driver restore applies; the startup-timeout half is deferred (never seen in ~60 runs) | [#75](https://github.com/6temes/rails_admin_next/issues/75) |
62
+ | `cc293dea` | Wait for the autocomplete menu itself, not for one of its entries | n/a — waits here use counts or text the stale menu can't satisfy; the widget renders once per query (as `77e5d0bc`) | — |
63
+ | `d3cfba1d` | Render the list for a filter URL written by hand | relevant — measured: seven malformed shapes 500, one specific to this fork | [#65](https://github.com/6temes/rails_admin_next/issues/65) |
64
+ | `ede4e77b` | Split the adapter layer into a Reflection and a Repository | declined — multi-ORM refactor, one adapter here; four embedded fixes filed separately | [#59](https://github.com/6temes/rails_admin_next/issues/59), [#66](https://github.com/6temes/rails_admin_next/issues/66), [#67](https://github.com/6temes/rails_admin_next/issues/67), [#68](https://github.com/6temes/rails_admin_next/issues/68) |
65
+ | `897ffc13` | Fix _discard leaking into SQL for multi-select enum filters | adapted — server half only; the client half was already stronger here (disabled options), so upstream's JS was deliberately not ported | [#36](https://github.com/6temes/rails_admin_next/pull/36) |
66
+ | `11f214f6` | Remove config options deprecated before RailsAdmin 3.0 | adapted — the deprecator half already shipped; this removed the six options | [#40](https://github.com/6temes/rails_admin_next/pull/40) |
67
+ | `cbea80b1` | Match GET in http_methods case-insensitively | adapted — `Actions::Base#linkable?` | [#38](https://github.com/6temes/rails_admin_next/pull/38) |
68
+ | `9336ec71` | Drop Proc bounds from a field's length validation | adapted — widened beyond upstream to Symbol bounds, which crash identically | [#35](https://github.com/6temes/rails_admin_next/pull/35) |
69
+ | `331841ea` | Remove duplicate global assignment of window.jQuery | n/a — jQuery removed | — |
70
+ | `71852cd8` | Fix malformed rubocop directive comment | n/a — measured: standardrb clean, and no `has_option?` directive here to malform | — |
71
+ | `cfc17e0d` | Pin json to < 3 for the test suite | n/a — dependency churn, Dependabot's job | — |
72
+ | `316c299d` | Change default response format of the show action to HTML | adapted | [#39](https://github.com/6temes/rails_admin_next/pull/39) |
73
+ | `0f4f765e` | Use the same widget which works for nullable booleans also for non nullable | declined — cosmetic consistency change; alters every boolean field in every host app and fixes no defect | — |
74
+ | `495cc864` | Filter ferrum 0.18's console stack-frame log lines | adapted — landed before the cuprite bump that would trigger it | [#37](https://github.com/6temes/rails_admin_next/pull/37) |
75
+ | `2eed28e1` | Fully specify all ESM imports with an extension | n/a — measured: every relative import in `src/` already carries `.js`; browser-native ESM requires it | — |
76
+ | `e8dec57a` | Upgrade @hotwired/turbo-rails to resolve security warning | n/a — turbo comes from the `turbo-rails` gem (2.0.23), which Dependabot reads | — |
77
+ | `07b2066a` | Replace `asset-url` with `url` in Sass files | n/a — no Sass; one hand-owned CSS file | — |
78
+ | `a0946615` | Drop support for Rails 6.x and Ruby 2.5/2.6 | n/a — this fork is Rails ~> 8.1 / Ruby >= 4.0.9 only | — |
79
+ | `0543cb3f` | Revert npm package.json version ahead of actual publish | n/a — no npm package | — |
80
+ | `cf7b112e` | Run Prettier on migrated docs | n/a — upstream's vendored wiki | — |
81
+ | `1c2b92a3` | Rewrite wiki cross-links to relative docs/ links | n/a — upstream's vendored wiki | — |
82
+ | `70ca765e` | Merge wiki history into docs/ | n/a — upstream vendored its GitHub wiki; this fork ships its own docs | — |
83
+ | `0690a5e6` | Start development for 4.0.0 | n/a — upstream release chore | — |
84
+ | `50fbbcd9` | Remove @babel/runtime as a direct dependency | n/a — no npm build here | — |
85
+ | `e4ad87b7` | Replace deprecated jQuery functions with native ones | n/a — jQuery removed | — |
86
+ | `77e5d0bc` | Improve intermittent autocomplete test failures due to stale element | n/a — measured: the widget stamps and aborts its own requests, and each query renders once | [#18](https://github.com/6temes/rails_admin_next/issues/18) |
87
+ | `26bb8763` | Declare JavaScript package is a module | n/a — no npm package | — |
88
+ | `336845f4` | Remove string mutation | adapted | [#16](https://github.com/6temes/rails_admin_next/pull/16) |
89
+ | `b72badeb` | Add a "sass" entry point to package.json | n/a — no npm/webpack build | — |
90
+ | `9b21e3ba` | Update rails/ujs NPM package | n/a — UJS removed, Turbo replaces it | — |
91
+ | `ca2d0954` | Use a non-beta trix package in dummy_app | n/a — trix is self-hosted from `action_text-trix`, not an npm pin | — |
92
+ | `1f681b48` | Add nonce to css/js tags | adapted — `csp_meta_tag` deliberately left out, see below | [#22](https://github.com/6temes/rails_admin_next/pull/22) |
93
+ | `54a23401` | Add belongs_to optional/required support to `required?` | adapted | [#13](https://github.com/6temes/rails_admin_next/pull/13) |
94
+ | `c18592a1` | Draw routes before the suite goes multi-threaded | adapted — premise corrected, see PR | [#17](https://github.com/6temes/rails_admin_next/pull/17) |
95
+ | `91e9cca8` | Resolve the inverse of a polymorphic association | adapted — 3 divergences, see below | [#14](https://github.com/6temes/rails_admin_next/pull/14) |
96
+ | `8bffeb61` | Pin vite-plugin-ruby below 5.2 | n/a — no vite | — |
97
+ | `9f03412d` | Fix RuboCop offense in main_controller_spec | n/a — linting is Standard Ruby | — |
98
+ | `17b64251` | Avoid carrierwave versions that pull in mimemagic | n/a — CarrierWave removed | — |
99
+ | `703e4894` | Add Rails 8.1 support | n/a — this fork is Rails ~> 8.1 only | — |
100
+ | `9881e254` | Add test for editing a nested one widget item | adapted — driven through this fork's markup | [#15](https://github.com/6temes/rails_admin_next/pull/15) |
101
+ | `47d913fe` | Fix flaky locale spec for Datetimepicker widget | n/a — native HTML5 date/time inputs replaced the widget | — |
102
+ | `ec685453` | Don't auto-require the appraisal gem | n/a — Appraisal removed | — |
103
+
104
+ ## Standing divergences
105
+
106
+ Where a port deliberately differs from upstream's own patch. Each is correct *for this fork*; changing one to match upstream would reintroduce a bug.
107
+
108
+ **`Association#inverse_of`** (from `91e9cca8`) resolves the declared `inverse_of:`, then the inverse ActiveRecord resolves for the reflection, then a polymorphic `as:` — and honours `inverse_of: false`. Upstream reads only the declared option and `as`.
109
+
110
+ - The middle step exists because this fork is ActiveRecord-only. Upstream's adapter also serves Mongoid, which resolves no inverse, so `as` is the only source available to both. Without the middle step, a conventional pair that declares nothing on either side reports no inverse and keeps rendering its back-reference.
111
+ - Honouring `inverse_of: false` matters because `false.try(:to_sym)` is `nil`, so upstream's `|| as` silently reinstates an inverse the host explicitly declined.
112
+ - The order is load-bearing. Reading the declared option first is what keeps a polymorphic `belongs_to` that declares an inverse from raising inside Rails' reflection, where the class cannot be computed.
113
+
114
+ **`FormBuilder#nested_field_association?`** (from the same commit) limits its hoisted name-match branch to association fields. Upstream's hoist is unconstrained. A polymorphic `as:` is never validated against the child class, so without the limit a plain column sharing that name can be suppressed from a subform.
115
+
116
+ **`sanitize_params_for!`** drops the parent's inverse from the nested allowlist, which upstream does not do at all — its permitted keys are derived purely from the visible fields. Hiding the back-reference without narrowing the allowlist left a collection subform re-parentable through a crafted `<assoc>_attributes[n]`, since `assign_nested_attributes_for_collection_association` assigns straight onto an already-associated record ([#24](https://github.com/6temes/rails_admin_next/pull/24)).
117
+
118
+ **`Field::Base#valid_length` drops Symbol bounds as well as Proc ones** (from `9336ec71`), and drops them only from the three keys that get compared (`LENGTH_BOUNDS`). Upstream drops every Proc-valued entry and no Symbols at all.
119
+
120
+ - ActiveModel sanctions four bound types and resolves Proc and Symbol against the record at validation time, so neither can be compared against the column limit. Measured here: `minimum: :some_method` raised the same `ArgumentError` upstream's fix addresses for Procs, and a surviving `maximum: :some_method` rendered "Length up to max_len" as help text.
121
+ - The narrowing to three keys exists because `valid_length` is a `register_instance_option` that host apps read. Rejecting every Proc, as upstream does, would also strip `if:`/`unless:` conditions from the hash.
122
+ - `:in` and `:within` need no entry: ActiveModel collapses them to `:minimum`/`:maximum` at validator construction, and a Proc passed as `:in` raises at declaration time.
123
+
124
+ **`StatementBuilder#to_statement` excludes `between` from the `_discard` strip** (from `897ffc13`), and strips only when the array actually contained the sentinel. Upstream strips unconditionally.
125
+
126
+ - `between` reads its value array by index, so removing an element shifts the range ends. Measured: `["ignored", "_discard", "20"]` became a `>= 20` filter where it had been `<= 20`.
127
+ - An array that arrived empty is not an array the strip emptied. A date filter legitimately submits `{v: [], o: "today"}`, carrying its meaning in the operator alone; returning early on any empty array breaks it.
128
+ - Upstream's client-side half is deliberately not ported. `filter_box_controller` here sets `disabled` on the sentinel options in multi-select mode, not merely `hidden`, and a disabled `<option>` is excluded from form submission — a stronger guarantee than upstream's deselect.
129
+
130
+ **The engine's stylesheet and style-preload tags carry the CSP nonce** (from `1f681b48`), but `csp_meta_tag` does not ship. Turbo reads that meta to re-nonce scripts it re-activates, and this engine renders no body script for it to re-activate; the nonce generator here is also per-request random, which a cached Turbo snapshot would carry stale. Adding the meta wants that generator decision alongside it.
131
+
132
+ ## Assessing the next batch
133
+
134
+ ```bash
135
+ git fetch upstream
136
+ git log --oneline --first-parent 897ffc13..upstream/master
137
+ ```
138
+
139
+ `--first-parent` is load-bearing. Upstream folds whole histories in from time to time — `70ca765e` merged its GitHub wiki into `docs/`, and a plain `git log` across it turns an 18-commit batch into 1301, the extra 1283 being someone else's markdown. Check anything that is a merge (`git log -1 --format='%P %s' <sha>`) for what it actually brought in: vendored docs are one row, a merged feature branch needs its commits triaged individually.
140
+
141
+ Triage each commit by the **underlying problem**, not by the API, selector, or library its diff happens to touch. State the problem without naming any of them; if that sentence describes something possible here, the commit is relevant even when its diff is entirely in code this fork deleted.
142
+
143
+ `77e5d0bc` above is the cautionary case, and it cuts both ways. It was first dismissed as jQuery-UI-only, which was the wrong reason — the race it describes is stated in terms of a selector, and dismissing it on that vocabulary is exactly the mistake the rule above exists to prevent. But the CI failure that seemed to confirm the re-triage turned out to be an unrelated bug: an example reading the database before the server had answered ([#23](https://github.com/6temes/rails_admin_next/pull/23)). The widget itself stamps and aborts its requests, and instrumenting it showed one render per query, so the upstream race has no counterpart here.
144
+
145
+ **A coincident failure is not confirmation.** Look past a commit's vocabulary to decide whether it *could* apply, then measure this fork's own code before concluding that it does.
@@ -22,7 +22,8 @@ module RailsAdminNext
22
22
  # table with attacker-chosen columns; the export form posts back to this same action.
23
23
  if format && !request.get?
24
24
  request.format = format
25
- @schema = HashHelper.symbolize(params[:schema].slice(:except, :include, :methods, :only).permit!.to_h) if params[:schema] # to_json and to_xml expect symbols for keys AND values.
25
+ serialization_schema # built while @action is still export
26
+ @schema = serialization_schema.sanitize(params[:schema].slice(:include, :methods, :only).permit!.to_h) if params[:schema]
26
27
  @objects = list_entries(@model_config, :export)
27
28
  index
28
29
  else
@@ -66,7 +66,7 @@ module RailsAdminNext
66
66
  @objects.collect { |object| {id: object.id.to_s, label: object.send(@model_config.object_label_method).to_s} }
67
67
  end
68
68
  else
69
- @objects.to_json(@schema)
69
+ @objects.to_json(@schema || serialization_schema.default)
70
70
  end
71
71
 
72
72
  if params[:send_data]
@@ -77,7 +77,7 @@ module RailsAdminNext
77
77
  end
78
78
 
79
79
  format.xml do
80
- output = @objects.map { |object| object.serializable_hash(@schema || {}) }.to_xml(root: @abstract_model.model.model_name.plural)
80
+ output = @objects.map { |object| object.serializable_hash(@schema || serialization_schema.default) }.to_xml(root: @abstract_model.model.model_name.plural)
81
81
  if params[:send_data]
82
82
  send_data output, filename: "#{params[:model_name]}_#{DateTime.now.strftime("%Y-%m-%d_%Hh%Mm%S")}.xml"
83
83
  else
@@ -24,7 +24,7 @@ module RailsAdminNext
24
24
  # registered type, so Rails falls back to whichever is declared first.
25
25
  respond_to do |format|
26
26
  format.html { render @action.template_name }
27
- format.json { render json: @object }
27
+ format.json { render json: @object.to_json(serialization_schema.default) }
28
28
  end
29
29
  end
30
30
  end
@@ -40,11 +40,13 @@ module RailsAdminNext
40
40
 
41
41
  # This is called when needing to scope a database query. It is called within the list
42
42
  # and bulk_delete/destroy actions and should return a scope which limits the records
43
- # to those which the user can perform the given action on.
43
+ # to those which the user can perform the given action on. A model with no policy
44
+ # Scope gets no records: bulk_delete deletes whatever this returns without a
45
+ # per-record check, so a forgotten Scope must not mean "everything".
44
46
  def query(_action, abstract_model)
45
47
  @controller.send(:policy_scope, abstract_model.model.all)
46
48
  rescue ::Pundit::NotDefinedError
47
- abstract_model.model.all
49
+ abstract_model.model.none
48
50
  end
49
51
 
50
52
  # This is called in the new/create actions to determine the initial attributes for new
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_admin_next/support/hash_helper"
4
+
5
+ module RailsAdminNext
6
+ # The options handed to to_json/serializable_hash, limited to what a section
7
+ # shows. Both serializers call every name under `methods` and `include` on each
8
+ # record, and serialize every attribute when `only` is missing, so a requested
9
+ # schema is only ever intersected with this, never passed through.
10
+ class SerializationSchema
11
+ def initialize(section, include_associations:)
12
+ @section = section
13
+ @include_associations = include_associations
14
+ end
15
+
16
+ # Attributes only: a virtual field need not be a method on the record, so
17
+ # calling it unasked could raise where the plain serialization never did.
18
+ def default
19
+ {only: allowed[:only]}
20
+ end
21
+
22
+ def sanitize(requested)
23
+ requested = HashHelper.symbolize(requested)
24
+ schema = filter(requested, allowed)
25
+ if @include_associations && requested[:include].is_a?(Hash)
26
+ schema[:include] = requested[:include].each_with_object({}) do |(name, value), included|
27
+ next unless value.is_a?(Hash) && allowed[:include].key?(name)
28
+
29
+ included[name] = filter(value, allowed[:include][name])
30
+ end
31
+ end
32
+ schema
33
+ end
34
+
35
+ private
36
+
37
+ def allowed
38
+ @allowed ||= begin
39
+ fields = @section.visible_fields
40
+ names = field_names(fields.select { |f| !f.association? || f.association.polymorphic? })
41
+ if @include_associations
42
+ names[:include] = fields.select { |f| f.association? && !f.association.polymorphic? }.to_h do |field|
43
+ associated = field.associated_model_config.export.with(@section.bindings.merge(object: field.associated_model_config.abstract_model.model.new))
44
+ [field.name, field_names(associated.visible_fields.reject(&:association?))]
45
+ end
46
+ else
47
+ # The section already shows these associations, so their keys expose nothing more
48
+ names[:only].concat(fields.select { |f| f.association? && f.association.type == :belongs_to && !f.association.polymorphic? }
49
+ .flat_map { |f| Array(f.association.foreign_key).map(&:to_sym) })
50
+ end
51
+ names
52
+ end
53
+ end
54
+
55
+ # Mirrors the names the export form offers (main/export.html.erb).
56
+ def field_names(fields)
57
+ fields.each_with_object({only: [], methods: []}) do |field, names|
58
+ list = field.virtual? ? names[:methods] : names[:only]
59
+ if field.association?
60
+ list << field.method_name.to_sym << field.association.foreign_type.to_sym
61
+ else
62
+ list << field.name.to_sym
63
+ end
64
+ end
65
+ end
66
+
67
+ # An empty `only` serializes no attribute in ActiveRecord, which is the point.
68
+ def filter(requested, permitted)
69
+ schema = {only: Array(requested[:only]) & permitted[:only]}
70
+ schema[:methods] = Array(requested[:methods]) & permitted[:methods] if requested.key?(:methods)
71
+ schema
72
+ end
73
+ end
74
+ end
@@ -6,8 +6,8 @@ module RailsAdminNext
6
6
  # the tag's version over these constants at build time, so the in-repo values are only
7
7
  # the floor a local `gem build` produces.
8
8
  MAJOR = 1
9
- MINOR = 1
10
- PATCH = 2
9
+ MINOR = 2
10
+ PATCH = 1
11
11
  PRE = nil
12
12
 
13
13
  class << self
@@ -12,6 +12,7 @@ require "rails_admin_next/extensions/paper_trail"
12
12
  require "rails_admin_next/support/csv_converter"
13
13
  require "rails_admin_next/support/hash_helper"
14
14
  require "rails_admin_next/support/paginated_collection"
15
+ require "rails_admin_next/support/serialization_schema"
15
16
  require "yaml"
16
17
 
17
18
  module RailsAdminNext
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: rails-admin-next
3
+ description: Configure or extend the rails_admin_next admin in a host Rails app (models, fields, custom actions and field types, CanCanCan/Pundit authorization, PaperTrail history, theming, CSP). Use instead of upstream RailsAdmin knowledge, which is wrong for this fork.
4
+ ---
5
+
6
+ # rails_admin_next
7
+
8
+ rails_admin_next is a hard fork of RailsAdmin: ActiveRecord only, Rails 8.1, importmap + Propshaft, Turbo + Stimulus, no build step. Most of the per-model DSL is unchanged from upstream, but the namespace, the asset stack and the frontend are not. Code written from upstream RailsAdmin memory is usually wrong here.
9
+
10
+ ## Read the installed gem first
11
+
12
+ Every `<gem>/` path below is relative to the directory this prints:
13
+
14
+ ```bash
15
+ bundle info --path rails_admin_next
16
+ ```
17
+
18
+ Read the version the app actually bundles before answering:
19
+
20
+ - `<gem>/docs/UPGRADING.md` lists every difference from upstream RailsAdmin.
21
+ - `<gem>/docs/security.md` covers what the engine enforces and what the host opts into.
22
+ - `<gem>/docs/upstream-parity.md` has a "Standing divergences" section on behavior that deliberately differs from upstream patches.
23
+ - `<gem>/lib/rails_admin_next/config/` is the DSL itself. When this skill and the source disagree, the source wins.
24
+
25
+ The upstream RailsAdmin wiki is still accurate for model, group and field options that the fork leaves unchanged. Check it against the source above before relying on it.
26
+
27
+ ## Naming: what was renamed, what was not
28
+
29
+ Renamed (write the new form):
30
+
31
+ - Ruby namespace `RailsAdminNext`, configured with `RailsAdminNext.config do |config| ... end` in config/initializers/rails_admin_next.rb.
32
+ - Mount: `mount RailsAdminNext::Engine => "/admin", as: "rails_admin_next"`. Keep the `rails_admin_next` alias, because the engine's views call that route proxy.
33
+ - Per-model macro `rails_admin_next do ... end`.
34
+ - Route helpers `rails_admin_next.dashboard_path`, `rails_admin_next_url`, and `bindings[:view].rails_admin_next` inside field blocks.
35
+
36
+ Not renamed (leave as is):
37
+
38
+ - The authorization subject is still `:rails_admin`. CanCanCan abilities grant `can :access, :rails_admin`. Never rewrite it to `:rails_admin_next`, which fails authorization.
39
+ - Action authorization keys (`:dashboard`, `:index`, `:show`, `:new`, `:edit`, `:export`, `:destroy`, `:history`, `:show_in_app`).
40
+ - The `admin.*` I18n namespace.
41
+
42
+ `bin/rails g rails_admin_next:install` writes the initializer and the mount.
43
+
44
+ ## Models, sections and fields
45
+
46
+ Configure a model either inline with the `rails_admin_next do ... end` macro or in the initializer with `config.model "Team" do ... end`. Both take the upstream DSL: `list`, `show`, `edit`, `create`, `update`, `export`, `nested` and `modal` sections, plus `field`, `configure`, `exclude_fields`, `group` and `navigation_icon`. Global options live on `config`, for example `included_models`, `excluded_models`, `default_items_per_page` and `label_methods`.
47
+
48
+ Field types live in `<gem>/lib/rails_admin_next/config/fields/types/`. A custom type subclasses one of them and registers with `RailsAdminNext::Config::Fields::Types.register(MyType)`. Register it under a name of your own, or the registration replaces the built-in type.
49
+
50
+ Read the "Required `belongs_to` associations" and "Nested forms hide the link back to the parent" sections of `<gem>/docs/UPGRADING.md` before changing association fields. Both behave differently from upstream.
51
+
52
+ ## Actions
53
+
54
+ Pick and order actions in the initializer:
55
+
56
+ ```ruby
57
+ RailsAdminNext.config do |config|
58
+ config.actions do
59
+ dashboard
60
+ index
61
+ new
62
+ export
63
+ bulk_delete
64
+ show
65
+ edit
66
+ delete
67
+ show_in_app
68
+ end
69
+ end
70
+ ```
71
+
72
+ A custom action subclasses `RailsAdminNext::Config::Actions::Base`, registers with `RailsAdminNext::Config::Actions.register(self)`, and then appears by name inside `config.actions`. Its options (`member`, `collection`, `http_methods`, `controller`, `link_icon`, `visible?`, `authorization_key`, `template_name` and the rest) are declared in `<gem>/lib/rails_admin_next/config/actions/base.rb`. `<gem>/lib/rails_admin_next/config/actions/show_in_app.rb` is a short example. The action renders the host view app/views/rails_admin_next/main/<template_name>.html.erb.
73
+
74
+ Icons take logical names, never Font Awesome classes: `link_icon :show_in_app` or `navigation_icon :list`. The list is in the "Icons" section of `<gem>/docs/UPGRADING.md`.
75
+
76
+ ## Authorization and history
77
+
78
+ - `config.authorize_with :cancancan` uses the app's `Ability`. Keep the `:rails_admin` subject (see Naming).
79
+ - `config.authorize_with :pundit` calls the record's policy with `<action>?` methods (`index?`, `edit?`, `dashboard?`) and scopes lists with `policy_scope`.
80
+ - `config.authorize_with do ... end` runs a custom block as a before filter.
81
+ - `config.authenticate_with do ... end` and `config.current_user_method do ... end` wire in the app's authentication, such as Devise or Warden.
82
+ - `config.audit_with :paper_trail` turns on the history action. Read the "History / audit table" section of `<gem>/docs/UPGRADING.md` first.
83
+
84
+ ## Frontend
85
+
86
+ - The host app must use Propshaft and importmap-rails. There is no Sprockets, Webpacker, Webpack, Vite or build step, and there is no `config.asset_source` option.
87
+ - The engine runs its own Stimulus application, exposed as `window.RailsAdminNext.application`. Register custom controllers on that instance, not on the host's Stimulus app.
88
+ - Add JavaScript to the admin through a host config/importmap.rails_admin.rb. The engine appends it after its own map, so it can add pins or override the entrypoint pin. The engine's pin names are in `<gem>/config/importmap.rails_admin.rb`.
89
+ - There is no jQuery, jQuery UI, Bootstrap JS or `@rails/ujs`. Confirmations use Turbo's `data-turbo-confirm`.
90
+ - Date and time fields are native HTML5 inputs, with no flatpickr. Rich text is ActionText/Trix. There are no CDN editors such as CKEditor, CodeMirror or froala.
91
+ - The admin supports only the latest Chrome and Safari, so modern CSS and DOM APIs are fair game.
92
+
93
+ ## Theming
94
+
95
+ Engine CSS sits in the cascade layers `ra.tokens`, `ra.framework` and `ra.skin`, so any unlayered host rule wins without specificity tricks. Rebrand by overriding `--ra-*` tokens, for example `:root { --ra-primary: #663399; }`. Upstream `--bs-*` variables do nothing here. The supported tokens and the `--bs-*` to `--ra-*` mapping are in the "Styling" section of `<gem>/docs/UPGRADING.md`. Dark mode follows the OS. `config.color_scheme = :light` (or `:dark`, or `:auto`) pins it.
96
+
97
+ ## Security
98
+
99
+ The engine always enforces these, and there is no option to weaken them: CSRF, `X-Frame-Options: SAMEORIGIN`, non-GET export, confirmation on delete, and visible-field slicing of submitted attributes. A Content Security Policy is opt-in through `config.content_security_policy do |policy| ... end`. Pass `report_only: true` while tuning it. Read `<gem>/docs/security.md` before writing the policy. It lists the inline styles that still need `:unsafe_inline` in `style-src`.
100
+
101
+ ## Never suggest
102
+
103
+ Mongoid, `RailsAdmin.config`, `rails_admin do` blocks, Font Awesome icon classes, jQuery plugins, Sprockets or Webpacker setup, or a migration from upstream RailsAdmin. The migration is a one-time rename, and `<gem>/docs/UPGRADING.md` covers it.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_admin_next
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel López Prat
@@ -189,6 +189,9 @@ files:
189
189
  - config/initializers/active_record_extensions.rb
190
190
  - config/locales/rails_admin_next.en.yml
191
191
  - config/routes.rb
192
+ - docs/UPGRADING.md
193
+ - docs/security.md
194
+ - docs/upstream-parity.md
192
195
  - lib/generators/rails_admin_next/install_generator.rb
193
196
  - lib/generators/rails_admin_next/templates/initializer.erb
194
197
  - lib/generators/rails_admin_next/utils.rb
@@ -295,8 +298,10 @@ files:
295
298
  - lib/rails_admin_next/support/csv_converter.rb
296
299
  - lib/rails_admin_next/support/hash_helper.rb
297
300
  - lib/rails_admin_next/support/paginated_collection.rb
301
+ - lib/rails_admin_next/support/serialization_schema.rb
298
302
  - lib/rails_admin_next/version.rb
299
303
  - lib/tasks/rails_admin.rake
304
+ - skills/rails-admin-next/SKILL.md
300
305
  - src/rails_admin/application.js
301
306
  - src/rails_admin/base.js
302
307
  - src/rails_admin/controllers/alert_controller.js
@@ -324,6 +329,7 @@ licenses:
324
329
  metadata:
325
330
  bug_tracker_uri: https://github.com/6temes/rails_admin_next/issues
326
331
  changelog_uri: https://github.com/6temes/rails_admin_next/releases
332
+ hyperdrive_targets: rails_admin_next
327
333
  rubygems_mfa_required: 'true'
328
334
  source_code_uri: https://github.com/6temes/rails_admin_next
329
335
  rdoc_options: []
@@ -333,7 +339,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
333
339
  requirements:
334
340
  - - ">="
335
341
  - !ruby/object:Gem::Version
336
- version: 4.0.5
342
+ version: 4.0.9
337
343
  required_rubygems_version: !ruby/object:Gem::Requirement
338
344
  requirements:
339
345
  - - ">="