senren-ui 0.1.4 → 0.1.6

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -2
  3. data/CONTRIBUTING.md +41 -8
  4. data/README.md +73 -11
  5. data/docs/components.md +222 -0
  6. data/docs/performance_testing.md +34 -0
  7. data/lib/commands/senren/add/add_command.rb +35 -0
  8. data/lib/generators/senren/install/install_generator.rb +4 -7
  9. data/lib/generators/senren/install/templates/base_component.rb.tt +39 -6
  10. data/lib/generators/senren/install/templates/conventions.md.tt +22 -8
  11. data/lib/senren/rails/agent_rules_writer.rb +175 -0
  12. data/lib/senren/rails/component_copier.rb +75 -6
  13. data/lib/senren/rails/component_installer.rb +47 -0
  14. data/lib/senren/rails/doctor.rb +26 -13
  15. data/lib/senren/rails/host_paths.rb +12 -3
  16. data/lib/senren/rails/installer.rb +4 -2
  17. data/lib/senren/rails/llms_writer.rb +5 -132
  18. data/lib/senren/rails/registry.rb +63 -31
  19. data/lib/senren/rails/skill_writer.rb +1 -1
  20. data/lib/senren/rails/version.rb +1 -1
  21. data/lib/senren/rails.rb +2 -0
  22. data/lib/tasks/senren.rake +26 -21
  23. data/templates/components/billing_plan_card/billing_plan_card_component.html.erb +1 -1
  24. data/templates/components/breadcrumb/breadcrumb_component.rb +2 -2
  25. data/templates/components/button/button_component.html.erb +1 -1
  26. data/templates/components/carousel/carousel_component.rb +1 -1
  27. data/templates/components/command/command_component.rb +1 -1
  28. data/templates/components/dropdown_menu/dropdown_menu_component.rb +10 -7
  29. data/templates/components/form/form_component.html.erb +8 -1
  30. data/templates/components/form/form_component.rb +3 -1
  31. data/templates/components/input/input_component.html.erb +1 -1
  32. data/templates/components/input/input_component.rb +19 -0
  33. data/templates/components/label/label_component.html.erb +1 -2
  34. data/templates/components/label/label_component.rb +12 -2
  35. data/templates/components/link/link_component.html.erb +1 -1
  36. data/templates/components/native_select/native_select_component.html.erb +19 -5
  37. data/templates/components/native_select/native_select_component.rb +17 -5
  38. data/templates/components/pagination/pagination_component.rb +2 -1
  39. data/templates/components/sidebar/sidebar_component.rb +2 -2
  40. data/templates/components/switch/switch_component.html.erb +2 -2
  41. data/templates/components/top_nav/top_nav_component.rb +2 -2
  42. data/templates/controllers/rich_text_editor_lite_controller.js +12 -2
  43. metadata +23 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82671c3e673cf8cf9d922e83685d72bf5448b9acb1073596a9643f3da6fc9c15
4
- data.tar.gz: 11691be1d0c35c8137aab34d0cb7ede42dc2b7a9f9d7f4cfa9b7a8b8acb1ba3e
3
+ metadata.gz: 58a8b09eaa25d57baa0dded24c204b71ba71addac1acf81ae27e08e148d39c06
4
+ data.tar.gz: 04b5733b08a0dfb54192e35c57bc289800988d9254a7c4ee1fcb6d66520299ee
5
5
  SHA512:
6
- metadata.gz: 52d5185842e8b35d4d3c6b2b9e3ec0015e1e09e436a830434f32a4f7fe64d846ba301c90d0229243a672cbfcc140e5104de5aef610ee8d33bc666414a08d83ee
7
- data.tar.gz: eaf517da0a216f79d74fa6bbd25873f4f510b283a4fcb0b948f60a0dc9a6966c9e09e7cfd4d55987eaefc7d0600fd8bc90170b1b278a9afb8ca71fec875d34bf
6
+ metadata.gz: 424219c05372db86c1df17e5274b5432b8fab5fbe82318fc760c7e23e3bd18beec2757e94abe5f2e21e788bd3326b5f2011c7554cb738192c5aca62588d5cf53
7
+ data.tar.gz: a517a125cb116c79c86094561f59e50b3c54d8ba29f0cdfde8a188b205b504ff1742ac2e14541996fb78fbcfe9a5e74152ba91de47293b3cea45c198373dd803
data/CHANGELOG.md CHANGED
@@ -7,6 +7,53 @@ 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.1.6] — 2026-06-09
11
+
12
+ ### Added
13
+
14
+ - Local preview app now seeds the full registered Senren component set and renders an exhaustive component kitchen sink.
15
+
16
+ ### Changed
17
+
18
+ - `bin/seed_preview` is now the canonical local preview seed command and targets `.local/preview`.
19
+ - `bin/seed_preview` writes a local-path gem entry that works for custom preview roots.
20
+ - `.rubocop.yml` target Ruby version now matches the gem runtime floor required by ViewComponent 4.x.
21
+
22
+ ### Fixed
23
+
24
+ - `safe_url` now accepts same-origin relative URLs such as `?page=:page`, `./settings`, and `settings` while still rejecting unsafe schemes and hosts.
25
+ - `ComponentCopier` applies the same URL rules when patching existing host apps.
26
+ - Local preview layout keeps the Tailwind browser compiler enabled so the preview renders correctly out of the box.
27
+
28
+ ## [0.1.5] — 2026-05-03
29
+
30
+ ### Added
31
+
32
+ - Multi-agent instruction sync system (`AgentRulesWriter`). A single
33
+ source-of-truth file (`.senren/agent-rules.md`) plus marker-managed
34
+ adapter files for Codex (`AGENTS.md`), Claude (`CLAUDE.md`),
35
+ Copilot (`.github/copilot-instructions.md`), and Cursor
36
+ (`.cursor/rules/senren.mdc`).
37
+ - New rake task `senren:agents:sync`.
38
+ - Plan 014 and Plan 015 documentation.
39
+
40
+ ### Changed
41
+
42
+ - `LlmsWriter` is now a thin backward-compatible wrapper that delegates
43
+ to `AgentRulesWriter`. No more `public/llms*.txt` generation.
44
+ - `senren:llms:generate` kept as deprecated alias.
45
+ - `Doctor` checks now validate agent instruction files instead of
46
+ `public/llms*.txt`.
47
+ - Doctor `run!` refactored into `runtime_checks` + `installation_checks`.
48
+ - Install generator no longer creates `public/` directory.
49
+
50
+ ### Fixed
51
+
52
+ - Deprecated `senren:llms:generate` task now passes `registry:` kwarg
53
+ consistently with all other call sites.
54
+ - Release checklist items updated to reflect agent sync system.
55
+ - Test assertion style standardized on Minitest-native `refute`.
56
+
10
57
  ## [0.1.4] — 2026-05-02
11
58
 
12
59
  ### Fixed
@@ -50,7 +97,7 @@ bug fixes only.
50
97
  - Tailwind design-token stylesheet (`senren.css`) with light/dark.
51
98
  - Centralized `.senren/skill.md` system with preserved user-region.
52
99
  - `public/llms.txt` and `public/llms-full.txt` generation.
53
- - `apps/todolist` Rails app dogfooding the gem via local path.
100
+ - A Rails dogfooding app for local-path gem integration.
54
101
  - Bun-based JS tooling for Stimulus templates:
55
102
  - `bun run controllers:syntax`
56
103
  - `bun run controllers:lint`
@@ -79,4 +126,4 @@ bug fixes only.
79
126
  ## [0.1.0] — 2026-04-27
80
127
 
81
128
  First tagged release once the Unreleased entries are validated end-to-end
82
- in `apps/todolist` per `plans/011_release_checklist.md`.
129
+ against the project dogfooding app per `plans/011_release_checklist.md`.
data/CONTRIBUTING.md CHANGED
@@ -17,24 +17,31 @@ 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
23
  bundle exec rake test
24
+ bin/system
25
+ bin/performance
26
+ bundle exec rubocop
24
27
  bun run controllers:check
25
28
  ```
26
29
 
27
- To exercise the gem against a real Rails app, use the bundled workspace:
30
+ To exercise the gem against a local host app inside this repo:
28
31
 
29
32
  ```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
33
+ cd /path/to/senren-ui
34
+ bin/seed_preview
35
+ cd .local/preview
35
36
  bin/rails server
37
+ # optional custom path:
38
+ # SENREN_PREVIEW_ROOT=/abs/path/to/your/preview-app bin/seed_preview
36
39
  ```
37
40
 
41
+ The local preview uses Tailwind's browser runtime for convenience. The
42
+ real documentation/reference app is maintained separately in
43
+ `senren-ui-page`.
44
+
38
45
  ## What to forbid in PRs
39
46
 
40
47
  - React, Vue, Alpine, lit, or any other JS framework dependency.
@@ -56,12 +63,38 @@ bin/rails server
56
63
  - System test in `test/system/` if interactive.
57
64
  - Registry entry in `registry/components.yml` (full schema).
58
65
  - Skill block produced by `SkillWriter`.
59
- - Demo usage in `apps/todolist` if relevant to the Todo UI.
66
+ - Demo usage in `.local/preview` if relevant to the local preview UI.
60
67
 
61
68
  ## Commit hygiene
62
69
 
63
70
  - One logical change per commit.
64
71
  - Mention the affected plan and history files in the commit body.
65
72
  - Run `bundle exec rake test` before pushing.
73
+ - Run `bin/system` before pushing if you touched component templates,
74
+ Stimulus controllers, or the dummy preview app.
75
+ - Run `bundle exec rubocop` before pushing.
76
+ - Run `bin/performance` before pushing if you touched component
77
+ templates, Stimulus controllers, or Importmap loading guidance.
66
78
  - Run `bun run controllers:check` before pushing if you touched
67
79
  `templates/controllers/*.js`.
80
+
81
+ ## Pull request workflow
82
+
83
+ 1. Fork the repo and branch from `main`.
84
+ 2. Keep each PR scoped to one logical change.
85
+ 3. If the change is architectural, add or update a matching `plans/`
86
+ entry first.
87
+ 4. Before opening the PR, add or update the matching `history/` file.
88
+ 5. Fill in the PR template with exact validation commands and results.
89
+
90
+ ## Security workflow
91
+
92
+ - Do not report vulnerabilities in public issues.
93
+ - Use GitHub Security Advisories or the contact in `SECURITY.md`.
94
+ - Do not commit API keys, credentials, or private tokens, even in tests
95
+ or screenshots.
96
+ - Do not pass untrusted URLs directly to component `href`/`src`
97
+ attributes. Use the shared `safe_url` / `safe_media_url` helpers.
98
+ - Do not use `raw`, `html_safe`, `innerHTML =`,
99
+ `insertAdjacentHTML`, `eval`, direct SQL APIs, or string-built SQL.
100
+ The security tests intentionally fail on these escape hatches.
data/README.md CHANGED
@@ -14,8 +14,7 @@ skill system and a source-copy install model inspired by shadcn/ui.
14
14
  - A registry of well-tested ViewComponents and Stimulus controllers.
15
15
  - A centralized `.senren/skill.md` file so AI coding agents understand
16
16
  every installed component, its dependencies, and its anti-patterns.
17
- - An `llms.txt` / `llms-full.txt` generator so AI agents can discover
18
- Senren without scraping your codebase.
17
+ - A multi-agent instruction sync for Codex, Claude, Copilot, and Cursor.
19
18
 
20
19
  ## What Senren is not
21
20
 
@@ -43,6 +42,10 @@ bin/rails senren:add button card badge alert form input \
43
42
  textarea native_select table dropdown_menu dialog alert_dialog
44
43
  ```
45
44
 
45
+ `senren:add` also works via `bundle exec rails senren:add ...`. The older
46
+ bracketed Rake task form, `bin/rails 'senren:add[button,card]'`, remains
47
+ supported for backward compatibility.
48
+
46
49
  ## Daily commands
47
50
 
48
51
  ```bash
@@ -51,11 +54,39 @@ bin/rails generate senren:component picker --client # custom component with Sti
51
54
  bin/rails generate senren:component picker --no-client # without Stimulus
52
55
  bin/rails senren:add dialog --client # install interactive official component
53
56
  bin/rails senren:add button # install static official component
57
+ bundle exec rails senren:add form input # equivalent alternate entry point
54
58
  bin/rails senren:skill:sync # rebuild .senren/skill.md
55
- bin/rails senren:llms:generate # rebuild public/llms*.txt
59
+ bin/rails senren:agents:sync # rebuild .senren/agent-rules + adapters
56
60
  bin/rails senren:doctor # check installation health
57
61
  ```
58
62
 
63
+ ## Keeping Stimulus JavaScript small
64
+
65
+ Senren copies only installed client controllers into your Rails app, but an
66
+ Importmap app can still download every controller on initial page load if it
67
+ keeps the default eager Stimulus loader and preload configuration. Once your
68
+ app has several interactive components, switch Stimulus to lazy loading:
69
+
70
+ ```javascript
71
+ // app/javascript/controllers/index.js
72
+ import { application } from "controllers/application"
73
+ import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
74
+
75
+ lazyLoadControllersFrom("controllers", application)
76
+ ```
77
+
78
+ Disable import-map preloading for those on-demand controller modules:
79
+
80
+ ```ruby
81
+ # config/importmap.rb
82
+ pin_all_from "app/javascript/controllers", under: "controllers", preload: false
83
+ ```
84
+
85
+ This keeps controllers mapped and usable while loading each module only when
86
+ its `data-controller` identifier appears in the page. Minification or source
87
+ maps are an application build/deployment decision; Senren deliberately ships
88
+ editable source controllers into the host app.
89
+
59
90
  ## Using a component
60
91
 
61
92
  ```erb
@@ -74,17 +105,27 @@ bin/rails senren:doctor # check installation health
74
105
 
75
106
  ## Workspace layout
76
107
 
77
- This repository ships as a workspace with a real Rails dogfooding app:
108
+ This repository ships as a gem source checkout with a git-ignored local
109
+ preview host:
78
110
 
79
111
  ```text
80
- senren-workspace/
81
- senren-rails/ # the local gem source directory
82
- apps/
83
- todolist/ # real Rails app that uses senren-ui via local path
112
+ senren-ui/
113
+ .local/
114
+ preview/ # local Rails preview host, ignored by git
115
+ ```
116
+
117
+ Use `bin/seed_preview` to create or refresh `.local/preview`. It
118
+ installs a small Senren component preview route, imports `senren.css`,
119
+ and loads Tailwind's browser runtime for local visual checks.
120
+
121
+ ```bash
122
+ bin/seed_preview
123
+ cd .local/preview
124
+ bin/rails server
84
125
  ```
85
126
 
86
- `apps/todolist` is the production-like acceptance test for Senren a
87
- small SaaS-style Todo manager built entirely from Senren components.
127
+ The full documentation/reference site lives outside this gem checkout in
128
+ `senren-ui-page`.
88
129
 
89
130
  ## AI Agent skill system
90
131
 
@@ -96,12 +137,17 @@ After install, your app contains:
96
137
  - `.senren/registry.yml` — mirror of the gem-side registry.
97
138
  - `.senren/installed_components.yml` — ledger of installed components.
98
139
  - `.senren/conventions.md` — Senren conventions for humans and agents.
99
- - `public/llms.txt`, `public/llms-full.txt` — discoverable AI summary.
140
+ - `.senren/agent-rules.md` — source of truth for generated agent rules.
141
+ - `AGENTS.md`, `CLAUDE.md`, `.github/copilot-instructions.md`,
142
+ `.cursor/rules/senren.mdc` — marker-managed adapter files for each agent.
100
143
 
101
144
  The skill file uses `<!-- senren:skill:start -->` / `:end` markers; only
102
145
  the region between them is rewritten by the generator, so any notes you
103
146
  add outside the markers are preserved.
104
147
 
148
+ Agent adapter files are also marker-managed, so Senren updates only its own
149
+ generated block and preserves your existing instructions outside that block.
150
+
105
151
  ## Component list
106
152
 
107
153
  See `registry/components.yml` for the canonical list. v0.1 ships:
@@ -122,6 +168,8 @@ See `registry/components.yml` for the canonical list. v0.1 ships:
122
168
  bundle install
123
169
  bun install
124
170
  bundle exec rake test # gem tests
171
+ bin/system # headless browser system tests
172
+ bin/performance # local payload/performance budgets
125
173
  bun run controllers:check # lint + syntax check for templates/controllers/*.js
126
174
  bun run controllers:lint:fix # auto-fix lint issues for controllers
127
175
  bundle exec rake test:system # Stimulus/system tests
@@ -135,6 +183,20 @@ See `CONTRIBUTING.md`. Two rules to know up front:
135
183
  2. Architectural decisions are captured in `plans/` before code is
136
184
  written.
137
185
 
186
+ ## Open source maintenance baseline
187
+
188
+ This repo ships with a GitHub baseline for safer public maintenance:
189
+
190
+ - CI on pull requests and `main` pushes for tests, RuboCop, and JS checks.
191
+ - CodeQL analysis for Ruby and JavaScript.
192
+ - Dependabot updates for Bundler, JS tooling, and GitHub Actions.
193
+ - PR and issue templates, `CODEOWNERS`, `CODE_OF_CONDUCT.md`, and
194
+ `SECURITY.md`.
195
+
196
+ Repository controls such as branch protection, required checks, release
197
+ permissions, and auto-delete branch still need to be enabled in GitHub
198
+ repository settings.
199
+
138
200
  ## License
139
201
 
140
202
  MIT — see `LICENSE`.
@@ -0,0 +1,222 @@
1
+ # Senren Components Guide
2
+
3
+ This guide covers the form-related components in detail, with runnable examples
4
+ and explicit guidance on common pitfalls.
5
+
6
+ ---
7
+
8
+ ## FormComponent
9
+
10
+ Wraps `form_with` with Senren semantic tokens and consistent spacing.
11
+
12
+ ### Key behavior
13
+
14
+ - **Yields a Rails form builder** (`|f|`) — use `f.text_field`, `f.select`, etc.
15
+ inside the block just like a normal Rails form.
16
+ - **`method:` defaults to `nil`** — Rails will infer `:post` for new records and
17
+ `:patch` for persisted records. Only pass `method:` explicitly when you need to
18
+ override this (e.g., `method: :delete`).
19
+ - **`model: nil` is safe** — model-less forms (login, search, password reset)
20
+ work without passing a model.
21
+
22
+ ### Examples
23
+
24
+ #### Basic model form (create)
25
+
26
+ ```erb
27
+ <%= render(Senren::FormComponent.new(model: @post, url: posts_path)) do |f| %>
28
+ <%= render(Senren::LabelComponent.new(for_field: "title", variant: :required)) { "Title" } %>
29
+ <%= f.text_field :title, class: "senren-input" %>
30
+ <%= render(Senren::ButtonComponent.new(variant: :primary, type: :submit)) { "Create" } %>
31
+ <% end %>
32
+ ```
33
+
34
+ #### Edit form (Rails infers PATCH automatically)
35
+
36
+ ```erb
37
+ <%= render(Senren::FormComponent.new(model: @post, url: post_path(@post))) do |f| %>
38
+ <%= render(Senren::LabelComponent.new(for_field: "title")) { "Title" } %>
39
+ <%= f.text_field :title, class: "senren-input" %>
40
+ <%= render(Senren::ButtonComponent.new(variant: :primary, type: :submit)) { "Update" } %>
41
+ <% end %>
42
+ ```
43
+
44
+ #### Model-less form (login)
45
+
46
+ ```erb
47
+ <%= render(Senren::FormComponent.new(url: session_path)) do |f| %>
48
+ <%= render(Senren::InputComponent.new(name: "email", type: "email", placeholder: "you@example.com")) %>
49
+ <%= render(Senren::InputComponent.new(name: "password", type: "password")) %>
50
+ <%= render(Senren::ButtonComponent.new(variant: :primary, type: :submit)) { "Sign in" } %>
51
+ <% end %>
52
+ ```
53
+
54
+ > **Warning**: Do NOT pass `method: :post` on edit forms for persisted models.
55
+ > Rails needs `method:` to be nil to infer PATCH, otherwise you'll get
56
+ > `No route matches [POST] "/resource/:id"`.
57
+
58
+ ---
59
+
60
+ ## InputComponent
61
+
62
+ ### ⚠️ InputComponent vs `form.text_field`
63
+
64
+ **`InputComponent` renders its own `<input>` tag.** It is a **replacement** for
65
+ `form.text_field`, not an add-on. Do not combine them.
66
+
67
+ #### ✅ Do
68
+
69
+ ```erb
70
+ <%# Option A: Use InputComponent standalone %>
71
+ <%= render(Senren::InputComponent.new(name: "email", type: "email")) %>
72
+
73
+ <%# Option B: Use Rails form builder with plain classes %>
74
+ <%= f.text_field :email, class: "your-input-classes" %>
75
+ ```
76
+
77
+ #### ❌ Do NOT
78
+
79
+ ```erb
80
+ <%# WRONG: This renders two inputs %>
81
+ <%= render(Senren::InputComponent.new(name: "email")) do %>
82
+ <%= f.text_field :email %>
83
+ <% end %>
84
+ ```
85
+
86
+ ### Required params
87
+
88
+ | Param | Type | Required | Default | Description |
89
+ |-------|------|----------|---------|-------------|
90
+ | `name` | String/Symbol | **Yes** | — | Input `name` attribute |
91
+ | `type` | String | No | `"text"` | HTML input type |
92
+ | `value` | String | No | `nil` | Pre-filled value |
93
+ | `placeholder` | String | No | `nil` | Placeholder text |
94
+ | `id` | String | No | auto | Defaults to parameterized `name` |
95
+ | `variant` | Symbol | No | `:default` | `:default` or `:error` |
96
+ | `size` | Symbol | No | `:md` | `:sm`, `:md`, or `:lg` |
97
+
98
+ ### File inputs
99
+
100
+ File inputs automatically get styled button treatment:
101
+
102
+ ```erb
103
+ <%= render(Senren::InputComponent.new(name: "avatar", type: "file")) %>
104
+ ```
105
+
106
+ The file selector button uses a segmented style with a divider, semantic surface
107
+ color, and pointer cursor — no additional classes needed.
108
+
109
+ ### Date/time inputs
110
+
111
+ Native date and datetime-local inputs work correctly. The component intentionally
112
+ omits `display: flex` from base styles because it breaks browser-native
113
+ date/time picker UI on some engines.
114
+
115
+ ```erb
116
+ <%= render(Senren::InputComponent.new(name: "starts_at", type: "datetime-local")) %>
117
+ <%= render(Senren::InputComponent.new(name: "due_date", type: "date")) %>
118
+ ```
119
+
120
+ ---
121
+
122
+ ## LabelComponent
123
+
124
+ ### Required params
125
+
126
+ | Param | Type | Required | Default | Description |
127
+ |-------|------|----------|---------|-------------|
128
+ | `for_field` | String | **Yes** | — | ID of the associated form control |
129
+ | `text` | String | No | `nil` | Fallback label text (if block is empty) |
130
+ | `variant` | Symbol | No | `:default` | `:default` or `:required` (adds `*`) |
131
+
132
+ ### Examples
133
+
134
+ Both patterns below are fully supported and produce identical output:
135
+
136
+ ```erb
137
+ <%# Block syntax %>
138
+ <%= render(Senren::LabelComponent.new(for_field: "name", variant: :required)) { "Student name" } %>
139
+
140
+ <%# Text param syntax (useful when block content might be empty) %>
141
+ <%= render(Senren::LabelComponent.new(for_field: "name", text: "Student name", variant: :required)) %>
142
+ ```
143
+
144
+ > **Note**: The `text:` param acts as a fallback. If both a block and `text:` are
145
+ > provided, the block content wins.
146
+
147
+ ---
148
+
149
+ ## NativeSelectComponent
150
+
151
+ Renders a native `<select>` element with Senren styling.
152
+
153
+ ### Key behavior
154
+
155
+ - **Defaults to native browser arrow** (`native_arrow: true`). This preserves
156
+ the platform's familiar select appearance (iOS wheel, Android dropdown, etc.).
157
+ - Pass `native_arrow: false` to use a custom SVG chevron overlay instead.
158
+
159
+ ### Required params
160
+
161
+ | Param | Type | Required | Default | Description |
162
+ |-------|------|----------|---------|-------------|
163
+ | `name` | String/Symbol | **Yes** | — | Select `name` attribute |
164
+ | `options` | Array | **Yes** | — | `["a","b"]` or `[["val","Label"],...]` |
165
+ | `selected` | String | No | `nil` | Pre-selected value |
166
+ | `prompt` | String | No | `nil` | Blank option text (e.g., "Choose…") |
167
+ | `native_arrow` | Boolean | No | `true` | Use native browser arrow |
168
+ | `variant` | Symbol | No | `:default` | `:default` or `:error` |
169
+
170
+ ### Examples
171
+
172
+ ```erb
173
+ <%# Native arrow (default — recommended) %>
174
+ <%= render(Senren::NativeSelectComponent.new(
175
+ name: "role",
176
+ options: [["admin", "Admin"], ["member", "Member"]],
177
+ prompt: "Choose role…"
178
+ )) %>
179
+
180
+ <%# Custom SVG arrow %>
181
+ <%= render(Senren::NativeSelectComponent.new(
182
+ name: "role",
183
+ options: [["admin", "Admin"], ["member", "Member"]],
184
+ native_arrow: false
185
+ )) %>
186
+ ```
187
+
188
+ ---
189
+
190
+ ## FormFieldComponent (pattern — not yet a shipped component)
191
+
192
+ A common pattern when building forms is wrapping label + control + error. Until a
193
+ dedicated `FormFieldComponent` ships, use this pattern:
194
+
195
+ ```erb
196
+ <div class="space-y-1.5">
197
+ <%= render(Senren::LabelComponent.new(for_field: "email", variant: :required)) { "Email" } %>
198
+ <%= render(Senren::InputComponent.new(name: "email", type: "email", variant: @errors[:email] ? :error : :default)) %>
199
+ <% if @errors[:email] %>
200
+ <p class="text-sm text-[hsl(var(--senren-destructive))]"><%= @errors[:email] %></p>
201
+ <% end %>
202
+ </div>
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Render syntax reminder
208
+
209
+ Always use **parentheses** around `render` when passing an inline content block:
210
+
211
+ ```erb
212
+ <%# ✅ Correct: parens around render %>
213
+ <%= render(Senren::ButtonComponent.new(variant: :primary)) { "Save" } %>
214
+
215
+ <%# ✅ Correct: do/end block %>
216
+ <%= render Senren::ButtonComponent.new(variant: :primary) do %>
217
+ Save
218
+ <% end %>
219
+
220
+ <%# ❌ Wrong: block attaches to .new, not render %>
221
+ <%= render Senren::ButtonComponent.new(variant: :primary) { "Save" } %>
222
+ ```
@@ -0,0 +1,34 @@
1
+ # Performance Testing
2
+
3
+ Senren uses CI-safe performance gates by default. The goal is to catch
4
+ regressions that this source-copy UI gem can control: template payload
5
+ growth, Stimulus controller payload growth, eager controller loading, and
6
+ runtime-heavy client behavior.
7
+
8
+ ## Commands
9
+
10
+ ```bash
11
+ bin/performance
12
+ bin/system
13
+ bin/ci
14
+ ```
15
+
16
+ `bin/performance` reads `config/performance_budgets.yml`.
17
+ `bin/system` runs headless browser tests against `test/dummy`.
18
+
19
+ `bin/system` uses Selenium with local Chromium/ChromeDriver by default.
20
+ Override paths with `SENREN_CHROME_BIN` and `SENREN_CHROMEDRIVER` if your
21
+ machine installs them somewhere else.
22
+
23
+ ## Benchmark Model
24
+
25
+ - Static budgets are deterministic and run on every PR.
26
+ - System tests verify browser behavior and coarse budgets.
27
+ - Lighthouse CI is deferred until a stable preview/docs URL exists.
28
+ - Competitive framework benchmarking is out of scope for normal CI.
29
+
30
+ ## Browser Budgets
31
+
32
+ System tests assert gross DOM size, controller loading, external resource,
33
+ and interaction-duration budgets. They intentionally avoid tight timing
34
+ thresholds because shared CI hardware is noisy.
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/command'
4
+ require 'senren/rails'
5
+
6
+ module Senren
7
+ module Command
8
+ class AddCommand < ::Rails::Command::Base
9
+ class_option :client, type: :boolean, default: nil,
10
+ desc: 'Override registry client behavior for installed components.'
11
+ class_option :force, type: :boolean, default: false,
12
+ desc: 'Overwrite existing component files.'
13
+
14
+ desc 'add COMPONENT [COMPONENT...]',
15
+ 'Install one or more Senren components into the current Rails app.'
16
+ def perform(*names)
17
+ require_application!
18
+
19
+ component_installer.install(
20
+ names: names,
21
+ client_override: options[:client],
22
+ force: options[:force]
23
+ )
24
+ rescue ArgumentError => e
25
+ raise ::Rails::Command::Base::Error, e.message
26
+ end
27
+
28
+ private
29
+
30
+ def component_installer
31
+ Senren::Rails::ComponentInstaller.new
32
+ end
33
+ end
34
+ end
35
+ end
@@ -27,10 +27,6 @@ module Senren
27
27
  empty_directory 'app/assets/stylesheets'
28
28
  end
29
29
 
30
- def create_public_dir
31
- empty_directory 'public'
32
- end
33
-
34
30
  def copy_base_files
35
31
  template 'base_component.rb.tt', 'app/components/senren/base_component.rb'
36
32
  template 'senren.css.tt', 'app/assets/stylesheets/senren.css'
@@ -47,14 +43,15 @@ module Senren
47
43
  Senren::Rails::SkillWriter.new(paths: host_paths).sync!
48
44
  end
49
45
 
50
- def write_llms_files
51
- say_status :senren, 'writing public/llms.txt and public/llms-full.txt'
52
- Senren::Rails::LlmsWriter.new(paths: host_paths).generate!
46
+ def write_agent_files
47
+ say_status :senren, 'syncing Codex/Cursor/Claude/Copilot instruction files'
48
+ Senren::Rails::AgentRulesWriter.new(paths: host_paths).sync!
53
49
  end
54
50
 
55
51
  def print_next_steps
56
52
  say "\nSenren installed."
57
53
  say 'Next: bin/rails senren:add button card badge alert dialog'
54
+ say 'Then: bin/rails senren:agents:sync'
58
55
  end
59
56
 
60
57
  private