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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +124 -2
  3. data/CONTRIBUTING.md +79 -9
  4. data/README.md +149 -14
  5. data/Rakefile +14 -1
  6. data/docs/components.md +222 -0
  7. data/docs/hot_reload.md +103 -0
  8. data/docs/performance_testing.md +38 -0
  9. data/lib/commands/senren/add/add_command.rb +35 -0
  10. data/lib/generators/senren/component/templates/controller.js.tt +7 -4
  11. data/lib/generators/senren/install/install_generator.rb +74 -0
  12. data/lib/generators/senren/install/templates/base_component.rb.tt +89 -8
  13. data/lib/generators/senren/install/templates/conventions.md.tt +26 -7
  14. data/lib/senren/rails/agent_rules_writer.rb +62 -19
  15. data/lib/senren/rails/asset_path_guard.rb +128 -0
  16. data/lib/senren/rails/base_component_patch.rb +64 -0
  17. data/lib/senren/rails/component_copier.rb +138 -18
  18. data/lib/senren/rails/component_installer.rb +73 -0
  19. data/lib/senren/rails/doctor.rb +7 -4
  20. data/lib/senren/rails/engine.rb +23 -0
  21. data/lib/senren/rails/host_paths.rb +11 -2
  22. data/lib/senren/rails/marker_block.rb +81 -0
  23. data/lib/senren/rails/registry.rb +73 -35
  24. data/lib/senren/rails/safe_write.rb +169 -0
  25. data/lib/senren/rails/skill_writer.rb +48 -12
  26. data/lib/senren/rails/version.rb +1 -1
  27. data/lib/senren/rails.rb +2 -1
  28. data/lib/senren-ui.rb +15 -0
  29. data/lib/tasks/senren.rake +53 -34
  30. data/registry/components.yml +45 -0
  31. data/registry/recipes.yml +12 -0
  32. data/templates/components/alert_dialog/alert_dialog_component.rb +1 -1
  33. data/templates/components/api_key_field/api_key_field_component.html.erb +1 -1
  34. data/templates/components/aspect_ratio/aspect_ratio_component.rb +7 -0
  35. data/templates/components/avatar/avatar_component.rb +8 -1
  36. data/templates/components/billing_plan_card/billing_plan_card_component.html.erb +1 -1
  37. data/templates/components/breadcrumb/breadcrumb_component.rb +2 -2
  38. data/templates/components/button/button_component.html.erb +1 -1
  39. data/templates/components/carousel/carousel_component.rb +1 -1
  40. data/templates/components/cart/cart_component.html.erb +61 -0
  41. data/templates/components/cart/cart_component.rb +71 -0
  42. data/templates/components/checkbox/checkbox_component.rb +1 -1
  43. data/templates/components/clipboard/clipboard_component.html.erb +1 -1
  44. data/templates/components/command/command_component.rb +2 -2
  45. data/templates/components/date_picker/date_picker_component.html.erb +1 -1
  46. data/templates/components/dialog/dialog_component.rb +1 -1
  47. data/templates/components/dropdown_menu/dropdown_menu_component.rb +10 -7
  48. data/templates/components/form/form_component.html.erb +8 -1
  49. data/templates/components/form/form_component.rb +12 -2
  50. data/templates/components/input/input_component.html.erb +1 -1
  51. data/templates/components/input/input_component.rb +19 -0
  52. data/templates/components/invite_member_dialog/invite_member_dialog_component.rb +1 -1
  53. data/templates/components/label/label_component.html.erb +1 -2
  54. data/templates/components/label/label_component.rb +12 -2
  55. data/templates/components/link/link_component.html.erb +1 -1
  56. data/templates/components/native_select/native_select_component.html.erb +19 -5
  57. data/templates/components/native_select/native_select_component.rb +17 -5
  58. data/templates/components/pagination/pagination_component.rb +2 -1
  59. data/templates/components/product_card/product_card_component.html.erb +38 -0
  60. data/templates/components/product_card/product_card_component.rb +49 -0
  61. data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.html.erb +1 -1
  62. data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.rb +1 -1
  63. data/templates/components/separator/separator_component.rb +7 -0
  64. data/templates/components/sheet/sheet_component.rb +1 -1
  65. data/templates/components/sidebar/sidebar_component.rb +2 -2
  66. data/templates/components/switch/switch_component.html.erb +2 -2
  67. data/templates/components/tooltip/tooltip_component.rb +2 -2
  68. data/templates/components/top_nav/top_nav_component.rb +2 -2
  69. data/templates/components/typography/typography_component.rb +7 -0
  70. data/templates/controllers/accordion_controller.js +1 -1
  71. data/templates/controllers/alert_dialog_controller.js +31 -7
  72. data/templates/controllers/cart_controller.js +83 -0
  73. data/templates/controllers/clipboard_controller.js +12 -1
  74. data/templates/controllers/command_controller.js +3 -4
  75. data/templates/controllers/context_menu_controller.js +38 -11
  76. data/templates/controllers/data_table_controller.js +8 -3
  77. data/templates/controllers/dialog_controller.js +47 -21
  78. data/templates/controllers/dropdown_menu_controller.js +40 -27
  79. data/templates/controllers/hover_card_controller.js +8 -0
  80. data/templates/controllers/invite_member_dialog_controller.js +6 -0
  81. data/templates/controllers/masked_input_controller.js +8 -1
  82. data/templates/controllers/popover_controller.js +25 -10
  83. data/templates/controllers/rich_text_editor_lite_controller.js +175 -28
  84. data/templates/controllers/sheet_controller.js +41 -11
  85. metadata +20 -6
  86. data/lib/senren/rails/installer.rb +0 -84
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e233d7736e61036693bb895d99e0fd11a37eb9e183979fa9ea4f1d17d1686354
4
- data.tar.gz: dbe764908e51d5007e876c412e2cdffeac6d2b36ac10b8b16187f376ef1e24f0
3
+ metadata.gz: d99681351df1dad873d945bf82b3070b3f8393c9d14b3ca65917262e6258ef6c
4
+ data.tar.gz: b487d9ac589b1d9ba2c304a5f53e4d93064b321d76e97a43ccfa1b9909a44b58
5
5
  SHA512:
6
- metadata.gz: 4a9bd5abdf7f39b9afd87d94e263b256c4e8fd226d5505bee5e1c7cd031e44fe917442b17479718fe39d23909ceee0f94e064073a7717b6309508471db18ce8b
7
- data.tar.gz: a40ab2909ac0f30900fe8e2aace6eba439820ac83cd2ccd37c6037bfcf372225c02d1268a721c3b38a4c243872c35b833ead558621a65a8399b9e8ea632d96b8
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
- - `apps/todolist` Rails app dogfooding the gem via local path.
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
- in `apps/todolist` per `plans/011_release_checklist.md`.
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-rails
20
+ cd senren-ui
21
21
  bundle install
22
22
  bun install
23
- bundle exec rake test
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
- To exercise the gem against a real Rails app, use the bundled workspace:
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 ../apps/todolist
31
- bundle install
32
- bin/rails db:setup
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 `apps/todolist` if relevant to the Todo UI.
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
- > 洗練 — refined, polished, sophisticated.
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
- ## What Senren is not
19
+ ## Scope
20
20
 
21
- - Not React, Vue, Alpine, or any external state framework.
22
- - Not a CSS-only kit components ship Ruby + ERB + (optional) Stimulus.
23
- - Not an opaque dependency installed components live in your app
24
- under `app/components/senren/` and you own them.
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 workspace with a real Rails dogfooding app:
170
+ This repository ships as a gem source checkout with a git-ignored local
171
+ preview host:
77
172
 
78
173
  ```text
79
- senren-workspace/
80
- senren-rails/ # the local gem source directory
81
- apps/
82
- todolist/ # real Rails app that uses senren-ui via local path
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
- `apps/todolist` is the production-like acceptance test for Senren a
86
- small SaaS-style Todo manager built entirely from Senren components.
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. v0.1 ships:
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'].exclude('test/system/**/*_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'