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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +49 -2
- data/CONTRIBUTING.md +41 -8
- data/README.md +73 -11
- data/docs/components.md +222 -0
- data/docs/performance_testing.md +34 -0
- data/lib/commands/senren/add/add_command.rb +35 -0
- data/lib/generators/senren/install/install_generator.rb +4 -7
- data/lib/generators/senren/install/templates/base_component.rb.tt +39 -6
- data/lib/generators/senren/install/templates/conventions.md.tt +22 -8
- data/lib/senren/rails/agent_rules_writer.rb +175 -0
- data/lib/senren/rails/component_copier.rb +75 -6
- data/lib/senren/rails/component_installer.rb +47 -0
- data/lib/senren/rails/doctor.rb +26 -13
- data/lib/senren/rails/host_paths.rb +12 -3
- data/lib/senren/rails/installer.rb +4 -2
- data/lib/senren/rails/llms_writer.rb +5 -132
- data/lib/senren/rails/registry.rb +63 -31
- data/lib/senren/rails/skill_writer.rb +1 -1
- data/lib/senren/rails/version.rb +1 -1
- data/lib/senren/rails.rb +2 -0
- data/lib/tasks/senren.rake +26 -21
- 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/command/command_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 +3 -1
- data/templates/components/input/input_component.html.erb +1 -1
- data/templates/components/input/input_component.rb +19 -0
- 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/sidebar/sidebar_component.rb +2 -2
- data/templates/components/switch/switch_component.html.erb +2 -2
- data/templates/components/top_nav/top_nav_component.rb +2 -2
- data/templates/controllers/rich_text_editor_lite_controller.js +12 -2
- metadata +23 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 58a8b09eaa25d57baa0dded24c204b71ba71addac1acf81ae27e08e148d39c06
|
|
4
|
+
data.tar.gz: 04b5733b08a0dfb54192e35c57bc289800988d9254a7c4ee1fcb6d66520299ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
30
|
+
To exercise the gem against a local host app inside this repo:
|
|
28
31
|
|
|
29
32
|
```bash
|
|
30
|
-
cd
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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
|
-
-
|
|
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:
|
|
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
|
|
108
|
+
This repository ships as a gem source checkout with a git-ignored local
|
|
109
|
+
preview host:
|
|
78
110
|
|
|
79
111
|
```text
|
|
80
|
-
senren-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
-
|
|
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`.
|
data/docs/components.md
ADDED
|
@@ -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
|
|
51
|
-
say_status :senren, '
|
|
52
|
-
Senren::Rails::
|
|
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
|