How It's Built
What the gem puts in your app, how its JavaScript is laid out, and what every change has to pass before it is published. Plain description of what the gem does today, as of v0.71.0.
The Ruby side
Every component is a ViewComponent under app/components/rapid_rails_ui/<name>/ with its own component.rb, styles.rb and USAGE.md, exposed through a rui_<name> helper. Rendering happens on the server: a chart, a table or a dialog is complete HTML before any JavaScript runs, and the JavaScript only adds behaviour on top.
Files the gem copies into your app are written to be re-run safely. bin/rails rui:update compares each file with the installed gem and reports it as unchanged or synced, so running it after every upgrade is the normal path, not a reinstall.
How the JavaScript is organized
All behaviour is Stimulus. The gem's JavaScript imports nothing but @hotwired/stimulus and its own files: there is no package.json in the gem and no third-party JavaScript library. The browser runs the same files whether your app uses importmap or jsbundling.
bin/rails rui:update (and the install generator) copy these into your app:
- One controller per interactive component in
app/javascript/controllers/rapid_rails_ui/, for examplechart_controller.js,dialog_controller.js,table_controller.js,combobox_controller.js. shared/turbo_fetch.js, the fetch helper the Turbo-aware controllers use.- Small shared controllers under
shared/that several components reuse: keyboard, popup, search, recent searches, voice search, switch and CSS highlight navigation. - The four chart modules under
shared/:chart_geometry.js,chart_format.js,tween.jsandtooltip_placement.js(added in 0.71.0). app/javascript/rapid_rails_ui/stimulus_guard.js, which registers every gem controller under its identifier and guards against collisions with your own controllers, plus a managed block incontrollers/index.jsthat calls it and, on importmap hosts, the pins inconfig/importmap.rb.
The copies are yours to read and debug, but not to edit: the next rui:update replaces a changed copy with the gem's version. Put app-specific behaviour in your own controllers.
Imports per bundler
The gem's source uses relative imports such as import { tween } from "./shared/tween", which esbuild resolves as is, so jsbundling hosts receive the files unchanged. Importmap has no relative resolution between pinned modules, so on importmap hosts rui:update rewrites every relative import to the pinned name, for example controllers/rapid_rails_ui/shared/tween.
This is why, on an importmap host, the copied chart_controller.js differs from the gem's file by exactly its import lines and nothing else. A copy that differs anywhere else has been edited by hand or is stale.
Testing and release gate
Every push and pull request on the gem runs:
- Ruby matrix: Ruby 3.3.3 with the latest dependencies and Ruby 3.2.2 with the oldest supported dependencies, running the full test suite, a second run with license enforcement switched on, and the
standardlinter. - JavaScript matrix: the Node tests for the shared chart modules on Node 20 and 22.
- Schema check on any change to the component schemas: the generated schema and
llmsfiles must be up to date with the source, and every YAML file must parse.
Publishing is a separate workflow that starts when a GitHub release is published (it can also be run by hand from the Actions tab). It runs the tests and the linter again, builds the gem, and pushes it to GitHub Packages and to Gemfury. A version that has not passed that workflow is not on either registry.
When a component's JavaScript fails
Since 0.73.1 a failure in a component's JavaScript says what failed and where, the way a Rails error does. The managed block in controllers/index.js loads the guard through loadGuard, wraps each registration, and watches for components Stimulus never connected. Each failure is logged as [RapidRailsUI] <component>#<id>: <message> (<cause>), the element gets data-rui-error, and in development a notice appears inside the component when the layout renders <%= rui_env_meta %>. bin/rails rui:doctor compares the copied files with the installed gem and prints the fix.
The messages, what each means, and the doctor's output are on the Troubleshooting page .