senren-ui 0.1.5 → 0.2.0
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 +4 -4
- data/CHANGELOG.md +124 -2
- data/CONTRIBUTING.md +79 -9
- data/README.md +149 -14
- data/Rakefile +14 -1
- data/docs/components.md +222 -0
- data/docs/hot_reload.md +103 -0
- data/docs/performance_testing.md +38 -0
- data/lib/commands/senren/add/add_command.rb +35 -0
- data/lib/generators/senren/component/templates/controller.js.tt +7 -4
- data/lib/generators/senren/install/install_generator.rb +74 -0
- data/lib/generators/senren/install/templates/base_component.rb.tt +89 -8
- data/lib/generators/senren/install/templates/conventions.md.tt +26 -7
- data/lib/senren/rails/agent_rules_writer.rb +62 -19
- data/lib/senren/rails/asset_path_guard.rb +128 -0
- data/lib/senren/rails/base_component_patch.rb +64 -0
- data/lib/senren/rails/component_copier.rb +138 -18
- data/lib/senren/rails/component_installer.rb +73 -0
- data/lib/senren/rails/doctor.rb +7 -4
- data/lib/senren/rails/engine.rb +23 -0
- data/lib/senren/rails/host_paths.rb +11 -2
- data/lib/senren/rails/marker_block.rb +81 -0
- data/lib/senren/rails/registry.rb +73 -35
- data/lib/senren/rails/safe_write.rb +169 -0
- data/lib/senren/rails/skill_writer.rb +48 -12
- data/lib/senren/rails/version.rb +1 -1
- data/lib/senren/rails.rb +2 -1
- data/lib/senren-ui.rb +15 -0
- data/lib/tasks/senren.rake +53 -34
- data/registry/components.yml +45 -0
- data/registry/recipes.yml +12 -0
- data/templates/components/alert_dialog/alert_dialog_component.rb +1 -1
- data/templates/components/api_key_field/api_key_field_component.html.erb +1 -1
- data/templates/components/aspect_ratio/aspect_ratio_component.rb +7 -0
- data/templates/components/avatar/avatar_component.rb +8 -1
- data/templates/components/billing_plan_card/billing_plan_card_component.html.erb +1 -1
- data/templates/components/breadcrumb/breadcrumb_component.rb +2 -2
- data/templates/components/button/button_component.html.erb +1 -1
- data/templates/components/carousel/carousel_component.rb +1 -1
- data/templates/components/cart/cart_component.html.erb +61 -0
- data/templates/components/cart/cart_component.rb +71 -0
- data/templates/components/checkbox/checkbox_component.rb +1 -1
- data/templates/components/clipboard/clipboard_component.html.erb +1 -1
- data/templates/components/command/command_component.rb +2 -2
- data/templates/components/date_picker/date_picker_component.html.erb +1 -1
- data/templates/components/dialog/dialog_component.rb +1 -1
- data/templates/components/dropdown_menu/dropdown_menu_component.rb +10 -7
- data/templates/components/form/form_component.html.erb +8 -1
- data/templates/components/form/form_component.rb +12 -2
- data/templates/components/input/input_component.html.erb +1 -1
- data/templates/components/input/input_component.rb +19 -0
- data/templates/components/invite_member_dialog/invite_member_dialog_component.rb +1 -1
- data/templates/components/label/label_component.html.erb +1 -2
- data/templates/components/label/label_component.rb +12 -2
- data/templates/components/link/link_component.html.erb +1 -1
- data/templates/components/native_select/native_select_component.html.erb +19 -5
- data/templates/components/native_select/native_select_component.rb +17 -5
- data/templates/components/pagination/pagination_component.rb +2 -1
- data/templates/components/product_card/product_card_component.html.erb +38 -0
- data/templates/components/product_card/product_card_component.rb +49 -0
- data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.html.erb +1 -1
- data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.rb +1 -1
- data/templates/components/separator/separator_component.rb +7 -0
- data/templates/components/sheet/sheet_component.rb +1 -1
- data/templates/components/sidebar/sidebar_component.rb +2 -2
- data/templates/components/switch/switch_component.html.erb +2 -2
- data/templates/components/tooltip/tooltip_component.rb +2 -2
- data/templates/components/top_nav/top_nav_component.rb +2 -2
- data/templates/components/typography/typography_component.rb +7 -0
- data/templates/controllers/accordion_controller.js +1 -1
- data/templates/controllers/alert_dialog_controller.js +31 -7
- data/templates/controllers/cart_controller.js +83 -0
- data/templates/controllers/clipboard_controller.js +12 -1
- data/templates/controllers/command_controller.js +3 -4
- data/templates/controllers/context_menu_controller.js +38 -11
- data/templates/controllers/data_table_controller.js +8 -3
- data/templates/controllers/dialog_controller.js +47 -21
- data/templates/controllers/dropdown_menu_controller.js +40 -27
- data/templates/controllers/hover_card_controller.js +8 -0
- data/templates/controllers/invite_member_dialog_controller.js +6 -0
- data/templates/controllers/masked_input_controller.js +8 -1
- data/templates/controllers/popover_controller.js +25 -10
- data/templates/controllers/rich_text_editor_lite_controller.js +175 -28
- data/templates/controllers/sheet_controller.js +41 -11
- metadata +20 -6
- data/lib/senren/rails/installer.rb +0 -84
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d99681351df1dad873d945bf82b3070b3f8393c9d14b3ca65917262e6258ef6c
|
|
4
|
+
data.tar.gz: b487d9ac589b1d9ba2c304a5f53e4d93064b321d76e97a43ccfa1b9909a44b58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f383e88dea3c90da1f1f93831bdf26c4bc5e00e1c63ffeb8ae53031d7c7e38c8afbaa30c445839cdbf8ad346acf247dcd04ae597c01b23704837daeb4eb4c2bf
|
|
7
|
+
data.tar.gz: 54e1f4ad10351c05906cdf1f864160cd3a0555e1698c0fa4944a728752cd97560300a9bae40a337a214a39d19eae87a3620fd4690e412d689b9430d022df0662
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,128 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
v0.x is a pre-stable line: minor bumps may break things; patch bumps are
|
|
8
8
|
bug fixes only.
|
|
9
9
|
|
|
10
|
+
## [0.2.0] — 2026-08-02
|
|
11
|
+
|
|
12
|
+
A hardening release. Most of it came out of an adversarial review of the whole
|
|
13
|
+
library; each item below was reproduced by running it before being fixed, and
|
|
14
|
+
pinned by a test that was watched failing first.
|
|
15
|
+
|
|
16
|
+
### Upgrading
|
|
17
|
+
|
|
18
|
+
Four changes alter existing behaviour. None requires a code change in your app,
|
|
19
|
+
but read these before upgrading:
|
|
20
|
+
|
|
21
|
+
- **`class:` now merges instead of replacing.** Previously
|
|
22
|
+
`ButtonComponent.new(variant: :primary, class: "mt-2")` rendered
|
|
23
|
+
`class="mt-2"` and dropped the variant and size styling entirely. It now
|
|
24
|
+
renders both. If you worked around the old behaviour by re-specifying every
|
|
25
|
+
utility, you can stop.
|
|
26
|
+
- **DOM ids are derived from component arguments, not random.** Ids are now
|
|
27
|
+
stable across renders, which is what makes Turbo morphing, fragment caching,
|
|
28
|
+
and ETags work. If you hard-coded a generated id in a test or a stylesheet,
|
|
29
|
+
it will have changed.
|
|
30
|
+
- **Booting with `app/components` on the asset load path now fails in
|
|
31
|
+
production.** See *Security* below. If you added that line for sidecar
|
|
32
|
+
assets, point it at `app/components/assets` instead.
|
|
33
|
+
- **`Senren::Rails::Installer` was removed.** It had no callers, and copying
|
|
34
|
+
`.tt` templates without rendering them would have written raw ERB into a host
|
|
35
|
+
app. `senren:install` is the supported path.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- `CartComponent` and `ProductCardComponent`, plus a `storefront` recipe.
|
|
40
|
+
The cart keeps a live subtotal and quantity steppers client-side and
|
|
41
|
+
announces changes with `senren--cart:changed`; the product tile submits a
|
|
42
|
+
form and ships no JavaScript, so listing pages stay light.
|
|
43
|
+
- `lib/senren-ui.rb`, so `gem "senren-ui"` loads the engine without a
|
|
44
|
+
`require:` option. Previously that form silently loaded nothing — no engine,
|
|
45
|
+
no rake tasks, and no asset guard — while the generator kept working.
|
|
46
|
+
- On-demand Stimulus loading, installed rather than documented.
|
|
47
|
+
`senren:install` switches `controllers/index.js` to `lazyLoadControllersFrom`
|
|
48
|
+
and adds `preload: false` to the controllers pin. It leaves a customised
|
|
49
|
+
`index.js` or a non-importmap app alone and reports what it did.
|
|
50
|
+
- `bin/watch`, which syncs template edits into the local preview app and
|
|
51
|
+
reloads the browser. Development-only; nothing ships to host apps.
|
|
52
|
+
- A Ruby 3.2–3.4 × Rails 7.1–8.1 test matrix, so the versions the gemspec
|
|
53
|
+
claims are the versions that are proved.
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- Overlay components (dialog, sheet, popover, dropdown, context menu, alert
|
|
58
|
+
dialog) drive their state through Stimulus values instead of writing to the
|
|
59
|
+
DOM directly, so state survives Turbo morphs.
|
|
60
|
+
- `--client` / `--no-client` applies only to the components you name.
|
|
61
|
+
It used to apply to the whole dependency closure, so
|
|
62
|
+
`senren:add context_menu --no-client` also stripped `dropdown_menu`'s
|
|
63
|
+
controller and the installed menu silently never opened.
|
|
64
|
+
- `.senren/skill.md` describes what was actually installed. It previously
|
|
65
|
+
reported the registry default, naming controller files that were not on disk.
|
|
66
|
+
- `.senren/registry.yml` refreshes on every install instead of drifting from
|
|
67
|
+
the gem after the first one.
|
|
68
|
+
- Rake helpers live in `SenrenRakeArgs` rather than as top-level methods on
|
|
69
|
+
`Object`, and argument scanning stops at the next rake task —
|
|
70
|
+
`rake 'senren:add[button]' db:seed` no longer tries to install `db:seed`.
|
|
71
|
+
- Documentation states the library's scope without characterising other
|
|
72
|
+
ecosystems.
|
|
73
|
+
|
|
74
|
+
### Security
|
|
75
|
+
|
|
76
|
+
- **Component source could be published in production.** With
|
|
77
|
+
`config.assets.paths << Rails.root.join("app/components")` — a line that
|
|
78
|
+
circulates as ViewComponent sidecar-asset guidance — Propshaft resolved every
|
|
79
|
+
component `.rb` and `.html.erb` as an asset, `assets:precompile` copied them
|
|
80
|
+
into `public/assets`, `.manifest.json` listed each by name, and the digested
|
|
81
|
+
URL returned Ruby source with `HTTP 200`. A boot check now raises in any
|
|
82
|
+
deployed environment and warns in development. Sidecar assets in their own
|
|
83
|
+
directory are unaffected.
|
|
84
|
+
- **The installer could write outside the application root.** A checkout
|
|
85
|
+
shipping `app/components/senren` as a symlink redirected copied files, and
|
|
86
|
+
the agent-adapter writers read their destination before rewriting it, so
|
|
87
|
+
content outside the checkout was modified too. All writes now go through one
|
|
88
|
+
containment layer that resolves symlinks, covers dangling links, and refuses
|
|
89
|
+
only paths that leave the root — an in-repo symlink such as
|
|
90
|
+
`ln -s AGENTS.md CLAUDE.md` keeps working.
|
|
91
|
+
- **`FormComponent#url` and `AvatarComponent#src` were unsanitised.** The first
|
|
92
|
+
reaches `form_with`'s action, where a protocol-relative URL sends every field
|
|
93
|
+
and the CSRF token off-origin. Both now use the same URL policy as the rest
|
|
94
|
+
of the library, and a property test covers every component rather than a list
|
|
95
|
+
of known ones.
|
|
96
|
+
- Rich-text paste is sanitised, and the two URL policies (markup versus typed
|
|
97
|
+
input) are separated so neither can promote a relative path to another origin.
|
|
98
|
+
- Five dependency advisories resolved, and `bundler-audit` is now a CI gate.
|
|
99
|
+
|
|
100
|
+
### Fixed
|
|
101
|
+
|
|
102
|
+
- `senren:doctor` reported success unconditionally.
|
|
103
|
+
- Marker-managed files could be corrupted by generated content containing
|
|
104
|
+
regexp backreferences, and could inject their own markers.
|
|
105
|
+
- `TypographyComponent`, `SeparatorComponent`, and `AspectRatioComponent`
|
|
106
|
+
raised on `.new` without an explicit variant.
|
|
107
|
+
- `data:` passed to any component dropped its `data-senren-component` marker.
|
|
108
|
+
- Eight components produced a new DOM id on every render.
|
|
109
|
+
- `date_picker` lost its height to a fused CSS class.
|
|
110
|
+
- Stimulus controllers no longer leak timers or document-level listeners across
|
|
111
|
+
Turbo navigations.
|
|
112
|
+
- Checkbox, radio, and switch controls are reachable by their accessible name.
|
|
113
|
+
|
|
114
|
+
## [0.1.6] — 2026-06-09
|
|
115
|
+
|
|
116
|
+
### Added
|
|
117
|
+
|
|
118
|
+
- Local preview app now seeds the full registered Senren component set and renders an exhaustive component kitchen sink.
|
|
119
|
+
|
|
120
|
+
### Changed
|
|
121
|
+
|
|
122
|
+
- `bin/seed_preview` is now the canonical local preview seed command and targets `.local/preview`.
|
|
123
|
+
- `bin/seed_preview` writes a local-path gem entry that works for custom preview roots.
|
|
124
|
+
- `.rubocop.yml` target Ruby version now matches the gem runtime floor required by ViewComponent 4.x.
|
|
125
|
+
|
|
126
|
+
### Fixed
|
|
127
|
+
|
|
128
|
+
- `safe_url` now accepts same-origin relative URLs such as `?page=:page`, `./settings`, and `settings` while still rejecting unsafe schemes and hosts.
|
|
129
|
+
- `ComponentCopier` applies the same URL rules when patching existing host apps.
|
|
130
|
+
- Local preview layout keeps the Tailwind browser compiler enabled so the preview renders correctly out of the box.
|
|
131
|
+
|
|
10
132
|
## [0.1.5] — 2026-05-03
|
|
11
133
|
|
|
12
134
|
### Added
|
|
@@ -79,7 +201,7 @@ bug fixes only.
|
|
|
79
201
|
- Tailwind design-token stylesheet (`senren.css`) with light/dark.
|
|
80
202
|
- Centralized `.senren/skill.md` system with preserved user-region.
|
|
81
203
|
- `public/llms.txt` and `public/llms-full.txt` generation.
|
|
82
|
-
-
|
|
204
|
+
- A Rails dogfooding app for local-path gem integration.
|
|
83
205
|
- Bun-based JS tooling for Stimulus templates:
|
|
84
206
|
- `bun run controllers:syntax`
|
|
85
207
|
- `bun run controllers:lint`
|
|
@@ -108,4 +230,4 @@ bug fixes only.
|
|
|
108
230
|
## [0.1.0] — 2026-04-27
|
|
109
231
|
|
|
110
232
|
First tagged release once the Unreleased entries are validated end-to-end
|
|
111
|
-
|
|
233
|
+
against the project dogfooding app per `plans/011_release_checklist.md`.
|
data/CONTRIBUTING.md
CHANGED
|
@@ -17,24 +17,61 @@ this file before opening a PR.
|
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
git clone <repo>
|
|
20
|
-
cd senren-
|
|
20
|
+
cd senren-ui
|
|
21
21
|
bundle install
|
|
22
22
|
bun install
|
|
23
|
-
|
|
23
|
+
bin/ci # every gate, reports all failures rather than the first
|
|
24
|
+
bin/ci --matrix # additionally run the Rails 7.1-8.1 matrix (slow)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Individual gates, if you want to run one in isolation:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bin/test # unit tests (no Rails app booted)
|
|
31
|
+
bundle exec rake test:integration # renders all 62 components via test/dummy
|
|
32
|
+
bin/system # headless browser tests
|
|
33
|
+
bin/performance # payload and runtime budgets
|
|
34
|
+
bin/matrix # unit tests on every supported Rails
|
|
35
|
+
bundle exec rubocop
|
|
36
|
+
bundle exec bundle-audit check --update
|
|
24
37
|
bun run controllers:check
|
|
25
38
|
```
|
|
26
39
|
|
|
27
|
-
|
|
40
|
+
`bin/system` needs no setup: it uses a system Chrome and
|
|
41
|
+
`/usr/bin/chromedriver` when present, and otherwise lets Selenium Manager
|
|
42
|
+
fetch a matching driver.
|
|
43
|
+
|
|
44
|
+
Supported versions are proved, not asserted. `gemfiles/rails_*.gemfile` drive
|
|
45
|
+
the matrix via `BUNDLE_GEMFILE`, CI runs Ruby 3.2-3.4 × Rails 7.1-8.1 with
|
|
46
|
+
`fail-fast: false`, and `test/gem_packaging_test.rb` fails if the gemspec's
|
|
47
|
+
floors drift out of that matrix.
|
|
48
|
+
|
|
49
|
+
To exercise the gem against a local host app inside this repo:
|
|
28
50
|
|
|
29
51
|
```bash
|
|
30
|
-
cd
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
bin/rails generate senren:install
|
|
34
|
-
bin/rails senren:add button card badge alert
|
|
52
|
+
cd /path/to/senren-ui
|
|
53
|
+
bin/seed_preview
|
|
54
|
+
cd .local/preview
|
|
35
55
|
bin/rails server
|
|
56
|
+
# optional custom path:
|
|
57
|
+
# SENREN_PREVIEW_ROOT=/abs/path/to/your/preview-app bin/seed_preview
|
|
36
58
|
```
|
|
37
59
|
|
|
60
|
+
While the server runs, start the watcher from the gem root in another
|
|
61
|
+
terminal so template edits appear without re-seeding:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bin/watch
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
It copies changed files from `templates/` and `registry/` into the preview
|
|
68
|
+
app and reloads the browser. See `docs/hot_reload.md` for what does and does
|
|
69
|
+
not reload.
|
|
70
|
+
|
|
71
|
+
The local preview uses Tailwind's browser runtime for convenience. The
|
|
72
|
+
real documentation/reference app is maintained separately in
|
|
73
|
+
`senren-ui-page`.
|
|
74
|
+
|
|
38
75
|
## What to forbid in PRs
|
|
39
76
|
|
|
40
77
|
- React, Vue, Alpine, lit, or any other JS framework dependency.
|
|
@@ -54,14 +91,47 @@ bin/rails server
|
|
|
54
91
|
(`app/javascript/controllers/senren/<name>_controller.js`).
|
|
55
92
|
- Component test in `test/components/`.
|
|
56
93
|
- System test in `test/system/` if interactive.
|
|
94
|
+
- Nothing extra is needed for render coverage: `test/integration/` renders
|
|
95
|
+
every registered component in every declared variant and size, driven
|
|
96
|
+
from the registry, so a new component is covered as soon as it has a
|
|
97
|
+
preview in `ComponentPreviewHelper`.
|
|
57
98
|
- Registry entry in `registry/components.yml` (full schema).
|
|
58
99
|
- Skill block produced by `SkillWriter`.
|
|
59
|
-
- Demo usage in
|
|
100
|
+
- Demo usage in `.local/preview` if relevant to the local preview UI.
|
|
60
101
|
|
|
61
102
|
## Commit hygiene
|
|
62
103
|
|
|
63
104
|
- One logical change per commit.
|
|
64
105
|
- Mention the affected plan and history files in the commit body.
|
|
106
|
+
- Run `bin/ci` before pushing.
|
|
107
|
+
- Run `bin/ci --matrix` if you touched the gemspec, the Gemfile, or anything
|
|
108
|
+
version sensitive.
|
|
65
109
|
- Run `bundle exec rake test` before pushing.
|
|
110
|
+
- Run `bin/system` before pushing if you touched component templates,
|
|
111
|
+
Stimulus controllers, or the dummy preview app.
|
|
112
|
+
- Run `bundle exec rubocop` before pushing.
|
|
113
|
+
- Run `bin/performance` before pushing if you touched component
|
|
114
|
+
templates, Stimulus controllers, or Importmap loading guidance.
|
|
66
115
|
- Run `bun run controllers:check` before pushing if you touched
|
|
67
116
|
`templates/controllers/*.js`.
|
|
117
|
+
|
|
118
|
+
## Pull request workflow
|
|
119
|
+
|
|
120
|
+
1. Fork the repo and branch from `main`.
|
|
121
|
+
2. Keep each PR scoped to one logical change.
|
|
122
|
+
3. If the change is architectural, add or update a matching `plans/`
|
|
123
|
+
entry first.
|
|
124
|
+
4. Before opening the PR, add or update the matching `history/` file.
|
|
125
|
+
5. Fill in the PR template with exact validation commands and results.
|
|
126
|
+
|
|
127
|
+
## Security workflow
|
|
128
|
+
|
|
129
|
+
- Do not report vulnerabilities in public issues.
|
|
130
|
+
- Use GitHub Security Advisories or the contact in `SECURITY.md`.
|
|
131
|
+
- Do not commit API keys, credentials, or private tokens, even in tests
|
|
132
|
+
or screenshots.
|
|
133
|
+
- Do not pass untrusted URLs directly to component `href`/`src`
|
|
134
|
+
attributes. Use the shared `safe_url` / `safe_media_url` helpers.
|
|
135
|
+
- Do not use `raw`, `html_safe`, `innerHTML =`,
|
|
136
|
+
`insertAdjacentHTML`, `eval`, direct SQL APIs, or string-built SQL.
|
|
137
|
+
The security tests intentionally fail on these escape hatches.
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Senren UI (`senren-ui`)
|
|
2
2
|
|
|
3
|
-
> 洗練 —
|
|
3
|
+
> 洗練 *(senren)* — Japanese for "refinement".
|
|
4
4
|
|
|
5
5
|
**[Live Docs & Component Reference → senren-ui.dev](https://www.senren-ui.dev)**
|
|
6
6
|
|
|
@@ -16,12 +16,19 @@ skill system and a source-copy install model inspired by shadcn/ui.
|
|
|
16
16
|
every installed component, its dependencies, and its anti-patterns.
|
|
17
17
|
- A multi-agent instruction sync for Codex, Claude, Copilot, and Cursor.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Scope
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
21
|
+
Senren renders on the server. Components are ViewComponent classes with ERB
|
|
22
|
+
templates, interactivity is Stimulus, and server state is Turbo's job. It is
|
|
23
|
+
not a client-side rendering layer: if your app is built around React, Vue, or
|
|
24
|
+
another client framework, a component library from that ecosystem will fit it
|
|
25
|
+
better than this one.
|
|
26
|
+
|
|
27
|
+
Two other things worth knowing up front:
|
|
28
|
+
|
|
29
|
+
- Components ship as Ruby + ERB + optional Stimulus, not as a CSS-only kit.
|
|
30
|
+
- They are copied into your app under `app/components/senren/`, so they are
|
|
31
|
+
yours to read and edit rather than an opaque dependency.
|
|
25
32
|
|
|
26
33
|
## Installation
|
|
27
34
|
|
|
@@ -42,6 +49,10 @@ bin/rails senren:add button card badge alert form input \
|
|
|
42
49
|
textarea native_select table dropdown_menu dialog alert_dialog
|
|
43
50
|
```
|
|
44
51
|
|
|
52
|
+
`senren:add` also works via `bundle exec rails senren:add ...`. The older
|
|
53
|
+
bracketed Rake task form, `bin/rails 'senren:add[button,card]'`, remains
|
|
54
|
+
supported for backward compatibility.
|
|
55
|
+
|
|
45
56
|
## Daily commands
|
|
46
57
|
|
|
47
58
|
```bash
|
|
@@ -50,11 +61,94 @@ bin/rails generate senren:component picker --client # custom component with Sti
|
|
|
50
61
|
bin/rails generate senren:component picker --no-client # without Stimulus
|
|
51
62
|
bin/rails senren:add dialog --client # install interactive official component
|
|
52
63
|
bin/rails senren:add button # install static official component
|
|
64
|
+
bundle exec rails senren:add form input # equivalent alternate entry point
|
|
53
65
|
bin/rails senren:skill:sync # rebuild .senren/skill.md
|
|
54
66
|
bin/rails senren:agents:sync # rebuild .senren/agent-rules + adapters
|
|
55
67
|
bin/rails senren:doctor # check installation health
|
|
56
68
|
```
|
|
57
69
|
|
|
70
|
+
### What reloads while you work
|
|
71
|
+
|
|
72
|
+
Components copied into your app are ordinary Rails code, so editing a
|
|
73
|
+
`*_component.rb` or `*.html.erb` takes effect on the next request with no
|
|
74
|
+
restart. The exception is Stimulus: a controller under
|
|
75
|
+
`app/javascript/controllers/senren/` is a module the browser has already
|
|
76
|
+
fetched, so editing it needs a page reload — the server does not need
|
|
77
|
+
restarting. `docs/hot_reload.md` has the details, including Turbo morphing.
|
|
78
|
+
|
|
79
|
+
## Keeping Stimulus JavaScript small
|
|
80
|
+
|
|
81
|
+
`bin/rails generate senren:install` wires on-demand controller loading for
|
|
82
|
+
you. It switches `app/javascript/controllers/index.js` from Rails' default
|
|
83
|
+
`eagerLoadControllersFrom` to `lazyLoadControllersFrom`, and adds
|
|
84
|
+
`preload: false` to the controllers pin in `config/importmap.rb`.
|
|
85
|
+
|
|
86
|
+
Without it, an Importmap app downloads **every** controller on **every** page.
|
|
87
|
+
`pin_all_from "app/javascript/controllers"` is recursive, so it covers
|
|
88
|
+
`app/javascript/controllers/senren` too — a static marketing page paid for the
|
|
89
|
+
rich text editor. With it, a module is fetched the first time its
|
|
90
|
+
`data-controller` identifier appears, including markup Turbo inserts later.
|
|
91
|
+
|
|
92
|
+
Two things worth knowing:
|
|
93
|
+
|
|
94
|
+
- It changes loading for **your** controllers as well, because it uses the
|
|
95
|
+
official `stimulus-loading` helper rather than a Senren-specific loader.
|
|
96
|
+
Lazy loading is the better default for almost every app, but it is your
|
|
97
|
+
call — the generator only edits the file while it still carries the
|
|
98
|
+
untouched Rails default, and reports what it did.
|
|
99
|
+
- If you have already customised `index.js`, or you bundle with esbuild or
|
|
100
|
+
Vite instead of Importmap, the generator leaves it alone and says so.
|
|
101
|
+
|
|
102
|
+
This used to be a paragraph asking you to do it by hand, and a CI check that
|
|
103
|
+
passed as long as the paragraph existed. It is now installed and asserted in
|
|
104
|
+
`test/generators/install_generator_test.rb` against the files `rails new`
|
|
105
|
+
actually produces.
|
|
106
|
+
|
|
107
|
+
## Component source in production
|
|
108
|
+
|
|
109
|
+
Senren copies editable ViewComponent, ERB and Stimulus source into your app.
|
|
110
|
+
That is the point of the design, and it means minification and source maps are
|
|
111
|
+
your app's decision, not the gem's — Senren ships readable source and gets out
|
|
112
|
+
of the way.
|
|
113
|
+
|
|
114
|
+
Readable in your repo is not the same as readable over HTTP, and there is one
|
|
115
|
+
configuration that turns the first into the second:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
# config/initializers/assets.rb — do NOT do this
|
|
119
|
+
Rails.application.config.assets.paths << Rails.root.join("app/components")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
That line appears in guidance about ViewComponent sidecar assets, and under
|
|
123
|
+
Propshaft it publishes the whole directory. Reproduced against Propshaft 1.3.2:
|
|
124
|
+
every `.rb` and `.html.erb` under `app/components` was resolved as an asset,
|
|
125
|
+
`RAILS_ENV=production rails assets:precompile` copied them into `public/assets`,
|
|
126
|
+
`public/assets/.manifest.json` listed each one next to its digested filename —
|
|
127
|
+
so the digest is not a secret — and requesting the digested URL returned the
|
|
128
|
+
Ruby source with `HTTP 200`, served by the web server without Rails involved.
|
|
129
|
+
|
|
130
|
+
Senren refuses to let this ship. A boot check runs after your initializers:
|
|
131
|
+
|
|
132
|
+
- **production** — raises, so the app does not boot and `assets:precompile`
|
|
133
|
+
fails. A failed deploy is recoverable; published source is not.
|
|
134
|
+
- **anywhere else** — prints a warning and carries on, because development is a
|
|
135
|
+
different risk calculation.
|
|
136
|
+
|
|
137
|
+
It only objects when component source actually sits under an asset path, so
|
|
138
|
+
sidecar assets remain available — put them in their own directory:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
Rails.application.config.assets.paths << Rails.root.join("app/components/assets")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The default configuration is unaffected: with no such line, every component
|
|
145
|
+
path returns `404`. The gem itself adds no production request-path code, and
|
|
146
|
+
this check is the sole exception.
|
|
147
|
+
|
|
148
|
+
See `test/asset_path_guard_test.rb` and
|
|
149
|
+
`test/integration/asset_path_guard_boot_test.rb`, which boot a real Rails app
|
|
150
|
+
with a real Propshaft and assert that precompile cannot publish source.
|
|
151
|
+
|
|
58
152
|
## Using a component
|
|
59
153
|
|
|
60
154
|
```erb
|
|
@@ -73,17 +167,27 @@ bin/rails senren:doctor # check installation health
|
|
|
73
167
|
|
|
74
168
|
## Workspace layout
|
|
75
169
|
|
|
76
|
-
This repository ships as a
|
|
170
|
+
This repository ships as a gem source checkout with a git-ignored local
|
|
171
|
+
preview host:
|
|
77
172
|
|
|
78
173
|
```text
|
|
79
|
-
senren-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
174
|
+
senren-ui/
|
|
175
|
+
.local/
|
|
176
|
+
preview/ # local Rails preview host, ignored by git
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Use `bin/seed_preview` to create or refresh `.local/preview`. It
|
|
180
|
+
installs a small Senren component preview route, imports `senren.css`,
|
|
181
|
+
and loads Tailwind's browser runtime for local visual checks.
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
bin/seed_preview
|
|
185
|
+
cd .local/preview
|
|
186
|
+
bin/rails server
|
|
83
187
|
```
|
|
84
188
|
|
|
85
|
-
|
|
86
|
-
|
|
189
|
+
The full documentation/reference site lives outside this gem checkout in
|
|
190
|
+
`senren-ui-page`.
|
|
87
191
|
|
|
88
192
|
## AI Agent skill system
|
|
89
193
|
|
|
@@ -108,7 +212,7 @@ generated block and preserves your existing instructions outside that block.
|
|
|
108
212
|
|
|
109
213
|
## Component list
|
|
110
214
|
|
|
111
|
-
See `registry/components.yml` for the canonical list.
|
|
215
|
+
See `registry/components.yml` for the canonical list. Currently shipping:
|
|
112
216
|
|
|
113
217
|
- **Phase 1 — Foundation** (full): Button, Link, Badge, Typography,
|
|
114
218
|
Separator, Skeleton, Avatar, Alert, Card, AspectRatio.
|
|
@@ -126,6 +230,8 @@ See `registry/components.yml` for the canonical list. v0.1 ships:
|
|
|
126
230
|
bundle install
|
|
127
231
|
bun install
|
|
128
232
|
bundle exec rake test # gem tests
|
|
233
|
+
bin/system # headless browser system tests
|
|
234
|
+
bin/performance # local payload/performance budgets
|
|
129
235
|
bun run controllers:check # lint + syntax check for templates/controllers/*.js
|
|
130
236
|
bun run controllers:lint:fix # auto-fix lint issues for controllers
|
|
131
237
|
bundle exec rake test:system # Stimulus/system tests
|
|
@@ -139,6 +245,35 @@ See `CONTRIBUTING.md`. Two rules to know up front:
|
|
|
139
245
|
2. Architectural decisions are captured in `plans/` before code is
|
|
140
246
|
written.
|
|
141
247
|
|
|
248
|
+
## Project health
|
|
249
|
+
|
|
250
|
+
Every push runs the test suite, RuboCop, and the JavaScript checks against
|
|
251
|
+
Ruby 3.2–3.4 × Rails 7.1–8.1. CodeQL scans Ruby and JavaScript, and Dependabot
|
|
252
|
+
tracks Bundler, JS tooling, and Actions.
|
|
253
|
+
|
|
254
|
+
Security reports go to `SECURITY.md`.
|
|
255
|
+
|
|
256
|
+
## Acknowledgements
|
|
257
|
+
|
|
258
|
+
Senren builds on ideas from prior art. No source code from either project is
|
|
259
|
+
included here — the influence is on architecture and developer experience, and
|
|
260
|
+
both are credited because ideas have authors even when licences do not require
|
|
261
|
+
the notice.
|
|
262
|
+
|
|
263
|
+
- [shadcn/ui](https://github.com/shadcn-ui/ui) (MIT) — the source-copy install
|
|
264
|
+
model: components are copied into your app so you own and can edit them,
|
|
265
|
+
rather than being rendered from inside a dependency.
|
|
266
|
+
- [jetrockets/jet_ui](https://github.com/jetrockets/jet_ui) (MIT, © 2026
|
|
267
|
+
JetRockets) — a Rails/ViewComponent UI library whose maintainers write openly
|
|
268
|
+
about the trade-offs they worked through. That writing informed
|
|
269
|
+
`plans/022_jet_ui_lessons.md`: how Tailwind's content scanning interacts with
|
|
270
|
+
components shipped from inside a gem, and the case for testing every Ruby and
|
|
271
|
+
Rails version a gem claims to support. Both points made this library better.
|
|
272
|
+
|
|
273
|
+
Both are MIT licensed, which permits use, modification, and study. The MIT
|
|
274
|
+
notice requirement attaches to copies of the software; since Senren copies no
|
|
275
|
+
code from either, this section is attribution rather than a licence obligation.
|
|
276
|
+
|
|
142
277
|
## License
|
|
143
278
|
|
|
144
279
|
MIT — see `LICENSE`.
|
data/Rakefile
CHANGED
|
@@ -6,11 +6,24 @@ require 'rake/testtask'
|
|
|
6
6
|
Rake::TestTask.new(:test) do |t|
|
|
7
7
|
t.libs << 'test'
|
|
8
8
|
t.libs << 'lib'
|
|
9
|
-
t.test_files = FileList['test/**/*_test.rb']
|
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
10
|
+
.exclude('test/system/**/*_test.rb')
|
|
11
|
+
.exclude('test/integration/**/*_test.rb')
|
|
10
12
|
t.warning = false
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
namespace :test do
|
|
16
|
+
# Separate process, like system tests, and for the same reason: the unit
|
|
17
|
+
# suite `load`s component classes directly, and booting the dummy app would
|
|
18
|
+
# load them a second time. ViewComponent 4 raises RedefinedSlotError on the
|
|
19
|
+
# duplicate declaration.
|
|
20
|
+
Rake::TestTask.new(:integration) do |t|
|
|
21
|
+
t.libs << 'test'
|
|
22
|
+
t.libs << 'lib'
|
|
23
|
+
t.test_files = FileList['test/integration/**/*_test.rb']
|
|
24
|
+
t.warning = false
|
|
25
|
+
end
|
|
26
|
+
|
|
14
27
|
Rake::TestTask.new(:system) do |t|
|
|
15
28
|
t.libs << 'test'
|
|
16
29
|
t.libs << 'lib'
|