senren-ui 0.1.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 +7 -0
- data/CHANGELOG.md +33 -0
- data/CONTRIBUTING.md +63 -0
- data/LICENSE +21 -0
- data/README.md +135 -0
- data/Rakefile +22 -0
- data/docs/visual_style.md +51 -0
- data/lib/generators/senren/component/component_generator.rb +62 -0
- data/lib/generators/senren/component/templates/component.html.erb.tt +3 -0
- data/lib/generators/senren/component/templates/component.rb.tt +13 -0
- data/lib/generators/senren/component/templates/component_test.rb.tt +16 -0
- data/lib/generators/senren/component/templates/controller.js.tt +23 -0
- data/lib/generators/senren/component/templates/system_test.rb.tt +7 -0
- data/lib/generators/senren/install/install_generator.rb +67 -0
- data/lib/generators/senren/install/templates/base_component.rb.tt +45 -0
- data/lib/generators/senren/install/templates/conventions.md.tt +66 -0
- data/lib/generators/senren/install/templates/installed_components.yml.tt +4 -0
- data/lib/generators/senren/install/templates/senren.css.tt +164 -0
- data/lib/senren/rails/component_copier.rb +111 -0
- data/lib/senren/rails/doctor.rb +86 -0
- data/lib/senren/rails/engine.rb +16 -0
- data/lib/senren/rails/host_paths.rb +36 -0
- data/lib/senren/rails/installer.rb +83 -0
- data/lib/senren/rails/llms_writer.rb +149 -0
- data/lib/senren/rails/registry.rb +161 -0
- data/lib/senren/rails/skill_writer.rb +166 -0
- data/lib/senren/rails/version.rb +7 -0
- data/lib/senren/rails.rb +39 -0
- data/lib/tasks/senren.rake +74 -0
- data/registry/components.yml +1053 -0
- data/registry/groups.yml +25 -0
- data/registry/recipes.yml +79 -0
- data/templates/components/accordion/accordion_component.html.erb +16 -0
- data/templates/components/accordion/accordion_component.rb +31 -0
- data/templates/components/activity_feed/activity_feed_component.html.erb +22 -0
- data/templates/components/activity_feed/activity_feed_component.rb +19 -0
- data/templates/components/alert/alert_component.html.erb +9 -0
- data/templates/components/alert/alert_component.rb +18 -0
- data/templates/components/alert_dialog/alert_dialog_component.html.erb +34 -0
- data/templates/components/alert_dialog/alert_dialog_component.rb +21 -0
- data/templates/components/api_key_field/api_key_field_component.html.erb +13 -0
- data/templates/components/api_key_field/api_key_field_component.rb +20 -0
- data/templates/components/app_shell/app_shell_component.html.erb +28 -0
- data/templates/components/app_shell/app_shell_component.rb +24 -0
- data/templates/components/aspect_ratio/aspect_ratio_component.html.erb +3 -0
- data/templates/components/aspect_ratio/aspect_ratio_component.rb +14 -0
- data/templates/components/avatar/avatar_component.html.erb +27 -0
- data/templates/components/avatar/avatar_component.rb +30 -0
- data/templates/components/badge/badge_component.html.erb +1 -0
- data/templates/components/badge/badge_component.rb +16 -0
- data/templates/components/billing_plan_card/billing_plan_card_component.html.erb +28 -0
- data/templates/components/billing_plan_card/billing_plan_card_component.rb +27 -0
- data/templates/components/breadcrumb/breadcrumb_component.html.erb +23 -0
- data/templates/components/breadcrumb/breadcrumb_component.rb +30 -0
- data/templates/components/bulk_action_bar/bulk_action_bar_component.html.erb +12 -0
- data/templates/components/bulk_action_bar/bulk_action_bar_component.rb +24 -0
- data/templates/components/button/button_component.html.erb +6 -0
- data/templates/components/button/button_component.rb +29 -0
- data/templates/components/calendar/calendar_component.html.erb +21 -0
- data/templates/components/calendar/calendar_component.rb +30 -0
- data/templates/components/card/card_component.html.erb +13 -0
- data/templates/components/card/card_component.rb +17 -0
- data/templates/components/carousel/carousel_component.html.erb +68 -0
- data/templates/components/carousel/carousel_component.rb +34 -0
- data/templates/components/checkbox/checkbox_component.html.erb +8 -0
- data/templates/components/checkbox/checkbox_component.rb +19 -0
- data/templates/components/checkbox_group/checkbox_group_component.html.erb +10 -0
- data/templates/components/checkbox_group/checkbox_group_component.rb +30 -0
- data/templates/components/clipboard/clipboard_component.html.erb +7 -0
- data/templates/components/clipboard/clipboard_component.rb +17 -0
- data/templates/components/codeblock/codeblock_component.html.erb +11 -0
- data/templates/components/codeblock/codeblock_component.rb +31 -0
- data/templates/components/collapsible/collapsible_component.html.erb +9 -0
- data/templates/components/collapsible/collapsible_component.rb +19 -0
- data/templates/components/combobox/combobox_component.html.erb +19 -0
- data/templates/components/combobox/combobox_component.rb +38 -0
- data/templates/components/command/command_component.html.erb +22 -0
- data/templates/components/command/command_component.rb +38 -0
- data/templates/components/context_menu/context_menu_component.html.erb +11 -0
- data/templates/components/context_menu/context_menu_component.rb +11 -0
- data/templates/components/data_table/data_table_component.html.erb +50 -0
- data/templates/components/data_table/data_table_component.rb +42 -0
- data/templates/components/date_picker/date_picker_component.html.erb +5 -0
- data/templates/components/date_picker/date_picker_component.rb +21 -0
- data/templates/components/dialog/dialog_component.html.erb +38 -0
- data/templates/components/dialog/dialog_component.rb +22 -0
- data/templates/components/dropdown_menu/dropdown_menu_component.html.erb +12 -0
- data/templates/components/dropdown_menu/dropdown_menu_component.rb +36 -0
- data/templates/components/empty_state/empty_state_component.html.erb +18 -0
- data/templates/components/empty_state/empty_state_component.rb +22 -0
- data/templates/components/filter_bar/filter_bar_component.html.erb +5 -0
- data/templates/components/filter_bar/filter_bar_component.rb +15 -0
- data/templates/components/form/form_component.html.erb +3 -0
- data/templates/components/form/form_component.rb +18 -0
- data/templates/components/hover_card/hover_card_component.html.erb +10 -0
- data/templates/components/hover_card/hover_card_component.rb +11 -0
- data/templates/components/input/input_component.html.erb +1 -0
- data/templates/components/input/input_component.rb +28 -0
- data/templates/components/invite_member_dialog/invite_member_dialog_component.html.erb +35 -0
- data/templates/components/invite_member_dialog/invite_member_dialog_component.rb +26 -0
- data/templates/components/label/label_component.html.erb +4 -0
- data/templates/components/label/label_component.rb +19 -0
- data/templates/components/link/link_component.html.erb +1 -0
- data/templates/components/link/link_component.rb +25 -0
- data/templates/components/masked_input/masked_input_component.html.erb +1 -0
- data/templates/components/masked_input/masked_input_component.rb +18 -0
- data/templates/components/native_select/native_select_component.html.erb +14 -0
- data/templates/components/native_select/native_select_component.rb +52 -0
- data/templates/components/page_header/page_header_component.html.erb +20 -0
- data/templates/components/page_header/page_header_component.rb +19 -0
- data/templates/components/pagination/pagination_component.html.erb +11 -0
- data/templates/components/pagination/pagination_component.rb +24 -0
- data/templates/components/popover/popover_component.html.erb +9 -0
- data/templates/components/popover/popover_component.rb +11 -0
- data/templates/components/progress/progress_component.html.erb +11 -0
- data/templates/components/progress/progress_component.rb +26 -0
- data/templates/components/radio_button/radio_button_component.html.erb +8 -0
- data/templates/components/radio_button/radio_button_component.rb +19 -0
- data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.html.erb +32 -0
- data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.rb +30 -0
- data/templates/components/search_input/search_input_component.html.erb +14 -0
- data/templates/components/search_input/search_input_component.rb +18 -0
- data/templates/components/select/select_component.html.erb +1 -0
- data/templates/components/select/select_component.rb +19 -0
- data/templates/components/separator/separator_component.html.erb +1 -0
- data/templates/components/separator/separator_component.rb +12 -0
- data/templates/components/settings_section/settings_section_component.html.erb +20 -0
- data/templates/components/settings_section/settings_section_component.rb +18 -0
- data/templates/components/sheet/sheet_component.html.erb +37 -0
- data/templates/components/sheet/sheet_component.rb +27 -0
- data/templates/components/shortcut_key/shortcut_key_component.html.erb +6 -0
- data/templates/components/shortcut_key/shortcut_key_component.rb +15 -0
- data/templates/components/sidebar/sidebar_component.html.erb +14 -0
- data/templates/components/sidebar/sidebar_component.rb +37 -0
- data/templates/components/skeleton/skeleton_component.html.erb +1 -0
- data/templates/components/skeleton/skeleton_component.rb +13 -0
- data/templates/components/stat_card/stat_card_component.html.erb +20 -0
- data/templates/components/stat_card/stat_card_component.rb +31 -0
- data/templates/components/switch/switch_component.html.erb +11 -0
- data/templates/components/switch/switch_component.rb +19 -0
- data/templates/components/table/table_component.html.erb +26 -0
- data/templates/components/table/table_component.rb +35 -0
- data/templates/components/tabs/tabs_component.html.erb +18 -0
- data/templates/components/tabs/tabs_component.rb +35 -0
- data/templates/components/team_member_list/team_member_list_component.html.erb +22 -0
- data/templates/components/team_member_list/team_member_list_component.rb +26 -0
- data/templates/components/textarea/textarea_component.html.erb +1 -0
- data/templates/components/textarea/textarea_component.rb +23 -0
- data/templates/components/theme_toggle/theme_toggle_component.html.erb +4 -0
- data/templates/components/theme_toggle/theme_toggle_component.rb +15 -0
- data/templates/components/tooltip/tooltip_component.html.erb +9 -0
- data/templates/components/tooltip/tooltip_component.rb +16 -0
- data/templates/components/top_nav/top_nav_component.html.erb +21 -0
- data/templates/components/top_nav/top_nav_component.rb +44 -0
- data/templates/components/typography/typography_component.html.erb +1 -0
- data/templates/components/typography/typography_component.rb +24 -0
- data/templates/controllers/accordion_controller.js +27 -0
- data/templates/controllers/alert_dialog_controller.js +38 -0
- data/templates/controllers/api_key_field_controller.js +36 -0
- data/templates/controllers/calendar_controller.js +16 -0
- data/templates/controllers/carousel_controller.js +50 -0
- data/templates/controllers/clipboard_controller.js +17 -0
- data/templates/controllers/collapsible_controller.js +13 -0
- data/templates/controllers/combobox_controller.js +64 -0
- data/templates/controllers/command_controller.js +80 -0
- data/templates/controllers/context_menu_controller.js +36 -0
- data/templates/controllers/data_table_controller.js +34 -0
- data/templates/controllers/date_picker_controller.js +17 -0
- data/templates/controllers/dialog_controller.js +50 -0
- data/templates/controllers/dropdown_menu_controller.js +92 -0
- data/templates/controllers/hover_card_controller.js +17 -0
- data/templates/controllers/invite_member_dialog_controller.js +28 -0
- data/templates/controllers/masked_input_controller.js +30 -0
- data/templates/controllers/popover_controller.js +42 -0
- data/templates/controllers/rich_text_editor_lite_controller.js +443 -0
- data/templates/controllers/select_controller.js +10 -0
- data/templates/controllers/sheet_controller.js +34 -0
- data/templates/controllers/sidebar_controller.js +10 -0
- data/templates/controllers/tabs_controller.js +41 -0
- data/templates/controllers/theme_toggle_controller.js +24 -0
- data/templates/controllers/tooltip_controller.js +10 -0
- metadata +257 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 789980b8355793bdfee36c7d930fe41a2c3ed379bb3a38b6960485c62acc9c69
|
|
4
|
+
data.tar.gz: d54ebbc90d3ae06c0ea76a3cb5c6d331afb1598eec69c372358399eb6cd1b5f7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f863e1bb2132548ee87c6eae7b113f3b0fad9ef2b6d388e4c9cf688852a14c43812c95dc75fdc08c2123dfb0de7541a5cf4c47b595e47c9eb23220a4f7cace6f
|
|
7
|
+
data.tar.gz: '039c3f45102ebef05ef9dcd4cf2c8f64c7bdc8faa710baeffbe4f87ed061afc547f9390b2a2a8fea708e773d17bac07e47e06c05a2115c66217100a1f0632445'
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `senren-ui` are recorded here.
|
|
4
|
+
|
|
5
|
+
This project follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
6
|
+
and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
v0.x is a pre-stable line: minor bumps may break things; patch bumps are
|
|
8
|
+
bug fixes only.
|
|
9
|
+
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Initial gem skeleton, engine, and version constant.
|
|
15
|
+
- Component registry (`registry/components.yml`, `groups.yml`,
|
|
16
|
+
`recipes.yml`) covering all Phase 1–6 components from the master plan.
|
|
17
|
+
- Library classes: `Registry`, `HostPaths`, `ComponentCopier`,
|
|
18
|
+
`SkillWriter`, `LlmsWriter`, `Installer`, `Doctor`.
|
|
19
|
+
- Generators: `senren:install`, `senren:component` (with `--client`).
|
|
20
|
+
- Rake tasks: `senren:add`, `senren:skill:sync`, `senren:llms:generate`,
|
|
21
|
+
`senren:doctor`.
|
|
22
|
+
- Phase 1–3 components fully implemented as ViewComponents.
|
|
23
|
+
- Phase 4–6 components scaffolded as registered stubs.
|
|
24
|
+
- Stimulus controllers for all interactive Phase 3 components.
|
|
25
|
+
- Tailwind design-token stylesheet (`senren.css`) with light/dark.
|
|
26
|
+
- Centralized `.senren/skill.md` system with preserved user-region.
|
|
27
|
+
- `public/llms.txt` and `public/llms-full.txt` generation.
|
|
28
|
+
- `apps/todolist` Rails app dogfooding the gem via local path.
|
|
29
|
+
|
|
30
|
+
## [0.1.0] — TBD
|
|
31
|
+
|
|
32
|
+
First tagged release once the Unreleased entries are validated end-to-end
|
|
33
|
+
in `apps/todolist` per `plans/011_release_checklist.md`.
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Contributing to Senren UI
|
|
2
|
+
|
|
3
|
+
Thanks for your interest. Senren is small and opinionated; please read
|
|
4
|
+
this file before opening a PR.
|
|
5
|
+
|
|
6
|
+
## Two non-negotiable rules
|
|
7
|
+
|
|
8
|
+
1. **Plans before code.** Architectural changes start with a new or
|
|
9
|
+
updated file in `plans/` describing Purpose, Scope, Decisions, Files
|
|
10
|
+
to create, Files to modify, Expected behavior, Test strategy, and
|
|
11
|
+
Acceptance criteria.
|
|
12
|
+
2. **History after work.** Every meaningful implementation session ends
|
|
13
|
+
with a new file in `history/YYYY-MM-DD-HHMM-<title>.md` recording
|
|
14
|
+
goal, changes, commands run, results, decisions, and next steps.
|
|
15
|
+
|
|
16
|
+
## Local setup
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
git clone <repo>
|
|
20
|
+
cd senren-rails
|
|
21
|
+
bundle install
|
|
22
|
+
bundle exec rake test
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
To exercise the gem against a real Rails app, use the bundled workspace:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd ../apps/todolist
|
|
29
|
+
bundle install
|
|
30
|
+
bin/rails db:setup
|
|
31
|
+
bin/rails generate senren:install
|
|
32
|
+
bin/rails senren:add button card badge alert
|
|
33
|
+
bin/rails server
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## What to forbid in PRs
|
|
37
|
+
|
|
38
|
+
- React, Vue, Alpine, lit, or any other JS framework dependency.
|
|
39
|
+
- Network calls from Stimulus controllers (Turbo handles server state).
|
|
40
|
+
- Hard-coded color utilities (`gray-*`, `slate-*`, etc.) in component
|
|
41
|
+
templates — use semantic tokens (`bg-background`, `text-foreground`).
|
|
42
|
+
- New per-component markdown files for AI agents — Senren uses one
|
|
43
|
+
centralized `.senren/skill.md`.
|
|
44
|
+
|
|
45
|
+
## Component checklist (Definition of Done)
|
|
46
|
+
|
|
47
|
+
- ViewComponent Ruby class under `Senren::` namespace.
|
|
48
|
+
- ERB template with `data-senren-component="<name>"` on root.
|
|
49
|
+
- Tailwind classes use semantic tokens only.
|
|
50
|
+
- Variants/sizes declared as class-level constants.
|
|
51
|
+
- Stimulus controller iff client behavior is needed
|
|
52
|
+
(`app/javascript/controllers/senren/<name>_controller.js`).
|
|
53
|
+
- Component test in `test/components/`.
|
|
54
|
+
- System test in `test/system/` if interactive.
|
|
55
|
+
- Registry entry in `registry/components.yml` (full schema).
|
|
56
|
+
- Skill block produced by `SkillWriter`.
|
|
57
|
+
- Demo usage in `apps/todolist` if relevant to the Todo UI.
|
|
58
|
+
|
|
59
|
+
## Commit hygiene
|
|
60
|
+
|
|
61
|
+
- One logical change per commit.
|
|
62
|
+
- Mention the affected plan and history files in the commit body.
|
|
63
|
+
- Run `bundle exec rake test` before pushing.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Senren UI Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Senren UI (`senren-ui`)
|
|
2
|
+
|
|
3
|
+
> 洗練 — refined, polished, sophisticated.
|
|
4
|
+
|
|
5
|
+
A Rails-native UI component library built on **ViewComponent**, **Hotwire
|
|
6
|
+
(Turbo + Stimulus)**, and **TailwindCSS**, with a centralized AI-agent
|
|
7
|
+
skill system and a source-copy install model inspired by shadcn/ui.
|
|
8
|
+
|
|
9
|
+
## What Senren is
|
|
10
|
+
|
|
11
|
+
- A Rails engine + generators that ship UI components into your host app.
|
|
12
|
+
- A registry of well-tested ViewComponents and Stimulus controllers.
|
|
13
|
+
- A centralized `.senren/skill.md` file so AI coding agents understand
|
|
14
|
+
every installed component, its dependencies, and its anti-patterns.
|
|
15
|
+
- An `llms.txt` / `llms-full.txt` generator so AI agents can discover
|
|
16
|
+
Senren without scraping your codebase.
|
|
17
|
+
|
|
18
|
+
## What Senren is not
|
|
19
|
+
|
|
20
|
+
- Not React, Vue, Alpine, or any external state framework.
|
|
21
|
+
- Not a CSS-only kit — components ship Ruby + ERB + (optional) Stimulus.
|
|
22
|
+
- Not an opaque dependency — installed components live in your app
|
|
23
|
+
under `app/components/senren/` and you own them.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Add to your Rails app's `Gemfile`:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
gem "senren-ui", path: "../../senren-rails", require: "senren/rails" # local path during dev
|
|
31
|
+
# or once published:
|
|
32
|
+
# gem "senren-ui", require: "senren/rails"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Then:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bundle install
|
|
39
|
+
bin/rails generate senren:install
|
|
40
|
+
bin/rails senren:add button card badge alert form input \
|
|
41
|
+
textarea native_select table dropdown_menu dialog alert_dialog
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Daily commands
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
bin/rails generate senren:install # one-time setup
|
|
48
|
+
bin/rails generate senren:component picker --client # custom component with Stimulus
|
|
49
|
+
bin/rails generate senren:component picker --no-client # without Stimulus
|
|
50
|
+
bin/rails senren:add dialog --client # install interactive official component
|
|
51
|
+
bin/rails senren:add button # install static official component
|
|
52
|
+
bin/rails senren:skill:sync # rebuild .senren/skill.md
|
|
53
|
+
bin/rails senren:llms:generate # rebuild public/llms*.txt
|
|
54
|
+
bin/rails senren:doctor # check installation health
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Using a component
|
|
58
|
+
|
|
59
|
+
```erb
|
|
60
|
+
<%= render Senren::ButtonComponent.new(variant: :primary) do %>
|
|
61
|
+
Save changes
|
|
62
|
+
<% end %>
|
|
63
|
+
|
|
64
|
+
<%= render Senren::CardComponent.new do |card| %>
|
|
65
|
+
<% card.with_header { "Account settings" } %>
|
|
66
|
+
<% card.with_body { "Manage your account details." } %>
|
|
67
|
+
<% card.with_footer do %>
|
|
68
|
+
<%= render(Senren::ButtonComponent.new(variant: :primary)) { "Save" } %>
|
|
69
|
+
<% end %>
|
|
70
|
+
<% end %>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Workspace layout
|
|
74
|
+
|
|
75
|
+
This repository ships as a workspace with a real Rails dogfooding app:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
senren-workspace/
|
|
79
|
+
senren-rails/ # the local gem source directory
|
|
80
|
+
apps/
|
|
81
|
+
todolist/ # real Rails app that uses senren-ui via local path
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`apps/todolist` is the production-like acceptance test for Senren — a
|
|
85
|
+
small SaaS-style Todo manager built entirely from Senren components.
|
|
86
|
+
|
|
87
|
+
## AI Agent skill system
|
|
88
|
+
|
|
89
|
+
After install, your app contains:
|
|
90
|
+
|
|
91
|
+
- `.senren/skill.md` — centralized component guide for AI agents,
|
|
92
|
+
grouped by Actions / Forms / Overlays / Navigation / Layout /
|
|
93
|
+
Data Display / SaaS Blocks / Rich Content.
|
|
94
|
+
- `.senren/registry.yml` — mirror of the gem-side registry.
|
|
95
|
+
- `.senren/installed_components.yml` — ledger of installed components.
|
|
96
|
+
- `.senren/conventions.md` — Senren conventions for humans and agents.
|
|
97
|
+
- `public/llms.txt`, `public/llms-full.txt` — discoverable AI summary.
|
|
98
|
+
|
|
99
|
+
The skill file uses `<!-- senren:skill:start -->` / `:end` markers; only
|
|
100
|
+
the region between them is rewritten by the generator, so any notes you
|
|
101
|
+
add outside the markers are preserved.
|
|
102
|
+
|
|
103
|
+
## Component list
|
|
104
|
+
|
|
105
|
+
See `registry/components.yml` for the canonical list. v0.1 ships:
|
|
106
|
+
|
|
107
|
+
- **Phase 1 — Foundation** (full): Button, Link, Badge, Typography,
|
|
108
|
+
Separator, Skeleton, Avatar, Alert, Card, AspectRatio.
|
|
109
|
+
- **Phase 2 — Forms** (full): Form, Input, Textarea, Checkbox,
|
|
110
|
+
CheckboxGroup, RadioButton, NativeSelect, Select, Switch, MaskedInput.
|
|
111
|
+
- **Phase 3 — Overlays** (full): Dialog, AlertDialog, DropdownMenu,
|
|
112
|
+
Popover, Tooltip, HoverCard, Sheet, ContextMenu.
|
|
113
|
+
- **Phases 4–6** (scaffolded stubs): Navigation/Layout, Data/Advanced,
|
|
114
|
+
SaaS Blocks. Each is registered, has a class, and renders a clearly
|
|
115
|
+
marked placeholder until promoted to full implementation.
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
bundle install
|
|
121
|
+
bundle exec rake test # gem tests
|
|
122
|
+
bundle exec rake test:system # Stimulus/system tests
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Contributing
|
|
126
|
+
|
|
127
|
+
See `CONTRIBUTING.md`. Two rules to know up front:
|
|
128
|
+
|
|
129
|
+
1. Every meaningful change creates or updates a file in `history/`.
|
|
130
|
+
2. Architectural decisions are captured in `plans/` before code is
|
|
131
|
+
written.
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT — see `LICENSE`.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rake/testtask'
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << 'test'
|
|
8
|
+
t.libs << 'lib'
|
|
9
|
+
t.test_files = FileList['test/**/*_test.rb'].exclude('test/system/**/*_test.rb')
|
|
10
|
+
t.warning = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
namespace :test do
|
|
14
|
+
Rake::TestTask.new(:system) do |t|
|
|
15
|
+
t.libs << 'test'
|
|
16
|
+
t.libs << 'lib'
|
|
17
|
+
t.test_files = FileList['test/system/**/*_test.rb']
|
|
18
|
+
t.warning = false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
task default: :test
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Senren Visual Style
|
|
2
|
+
|
|
3
|
+
Senren's default visual direction is **Spring Garden SaaS**: modern Rails UI
|
|
4
|
+
components with the color warmth of Japanese garden illustration, the tactile
|
|
5
|
+
quality of risograph print, and the restraint needed for real product screens.
|
|
6
|
+
|
|
7
|
+
## Vocabulary
|
|
8
|
+
|
|
9
|
+
- **Spring**: fresh, bright, optimistic color without candy-like saturation.
|
|
10
|
+
- **Japanese garden**: pond blue, sakura pink, young leaf green, iris violet,
|
|
11
|
+
warm paper, and deep pine ink.
|
|
12
|
+
- **Risograph-adjacent**: flat color fields, gentle grain when used in
|
|
13
|
+
marketing surfaces, and clear shape boundaries.
|
|
14
|
+
- **SaaS-practical**: readable tables, forms, dialogs, and dashboards. Color
|
|
15
|
+
supports hierarchy; it does not replace hierarchy.
|
|
16
|
+
- **Elegant**: small radius, calm borders, controlled shadows, generous but
|
|
17
|
+
not decorative spacing.
|
|
18
|
+
|
|
19
|
+
## Core Palette
|
|
20
|
+
|
|
21
|
+
Use the semantic `--senren-*` tokens in component templates. The named palette
|
|
22
|
+
tokens are available for documentation, examples, and branded surfaces.
|
|
23
|
+
|
|
24
|
+
| Token | Role |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `--senren-background` | warm paper app background |
|
|
27
|
+
| `--senren-foreground` | deep pine ink text |
|
|
28
|
+
| `--senren-primary` | main action green |
|
|
29
|
+
| `--senren-secondary` | sakura surface |
|
|
30
|
+
| `--senren-accent` | pond blue hover and highlight |
|
|
31
|
+
| `--senren-muted` | soft young leaf surface |
|
|
32
|
+
| `--senren-border` | pale waterline borders |
|
|
33
|
+
| `--senren-palette-sky` | illustration and brand accent |
|
|
34
|
+
| `--senren-palette-sakura` | illustration and secondary surfaces |
|
|
35
|
+
| `--senren-palette-pond` | panels, previews, and selected surfaces |
|
|
36
|
+
| `--senren-palette-leaf` | success and growth accents |
|
|
37
|
+
| `--senren-palette-iris` | secondary visual accent |
|
|
38
|
+
| `--senren-palette-paper` | warm neutral surface |
|
|
39
|
+
|
|
40
|
+
## Component Rules
|
|
41
|
+
|
|
42
|
+
- Prefer semantic tokens over raw color utilities.
|
|
43
|
+
- Keep primitives quiet: buttons, inputs, cards, tables, menus, and dialogs
|
|
44
|
+
must stay usable in dense SaaS screens.
|
|
45
|
+
- Use colorful surfaces mostly for state, focus, hover, badges, empty states,
|
|
46
|
+
callouts, and documentation previews.
|
|
47
|
+
- Avoid black-and-white defaults unless contrast requires it.
|
|
48
|
+
- Avoid one-hue themes; every screen should have a warm neutral, green, blue,
|
|
49
|
+
and one soft floral accent available.
|
|
50
|
+
- Do not use noisy grain inside core form controls, tables, or menus. Grain is
|
|
51
|
+
for marketing areas, illustrations, and preview frames only.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators/named_base'
|
|
4
|
+
require 'senren/rails'
|
|
5
|
+
|
|
6
|
+
module Senren
|
|
7
|
+
module Generators
|
|
8
|
+
# Low-level generator for creating a custom component in the host app.
|
|
9
|
+
#
|
|
10
|
+
# bin/rails generate senren:component picker
|
|
11
|
+
# bin/rails generate senren:component picker --no-client
|
|
12
|
+
class ComponentGenerator < ::Rails::Generators::NamedBase
|
|
13
|
+
source_root File.expand_path('templates', __dir__)
|
|
14
|
+
|
|
15
|
+
class_option :client, type: :boolean, default: true,
|
|
16
|
+
desc: 'Generate a Stimulus controller alongside the component.'
|
|
17
|
+
|
|
18
|
+
def create_component_class
|
|
19
|
+
template 'component.rb.tt',
|
|
20
|
+
"app/components/senren/#{file_name}_component.rb"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def create_component_template
|
|
24
|
+
template 'component.html.erb.tt',
|
|
25
|
+
"app/components/senren/#{file_name}_component.html.erb"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create_component_test
|
|
29
|
+
template 'component_test.rb.tt',
|
|
30
|
+
"test/components/senren/#{file_name}_component_test.rb"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create_stimulus_controller
|
|
34
|
+
return unless options[:client]
|
|
35
|
+
|
|
36
|
+
template 'controller.js.tt',
|
|
37
|
+
"app/javascript/controllers/senren/#{file_name}_controller.js"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def create_system_test
|
|
41
|
+
return unless options[:client]
|
|
42
|
+
|
|
43
|
+
template 'system_test.rb.tt',
|
|
44
|
+
"test/system/senren/#{file_name}_test.rb"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def file_name
|
|
50
|
+
super.underscore
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def class_name
|
|
54
|
+
file_name.camelize
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def stimulus_identifier
|
|
58
|
+
"senren--#{file_name.dasherize}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class Senren::<%= class_name %>ComponentTest < ViewComponent::TestCase
|
|
4
|
+
test "renders with default variant" do
|
|
5
|
+
render_inline(Senren::<%= class_name %>Component.new) { "hello" }
|
|
6
|
+
|
|
7
|
+
assert_selector "[data-senren-component='<%= file_name %>']"
|
|
8
|
+
assert_text "hello"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
test "rejects unknown variant" do
|
|
12
|
+
assert_raises(ArgumentError) do
|
|
13
|
+
Senren::<%= class_name %>Component.new(variant: :nope)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Stimulus controller for Senren <%= class_name %>.
|
|
4
|
+
// Identifier: <%= stimulus_identifier %>
|
|
5
|
+
//
|
|
6
|
+
// Responsibilities (local UI only):
|
|
7
|
+
// - Manage open/close, focus, or keyboard behavior for this component.
|
|
8
|
+
// Forbidden:
|
|
9
|
+
// - fetch / XHR (Turbo handles server state)
|
|
10
|
+
// - importing React, Vue, Alpine, lit, or any other framework
|
|
11
|
+
export default class extends Controller {
|
|
12
|
+
static targets = []
|
|
13
|
+
static values = {}
|
|
14
|
+
static classes = []
|
|
15
|
+
|
|
16
|
+
connect() {
|
|
17
|
+
// initialization
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
disconnect() {
|
|
21
|
+
// cleanup
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
require "application_system_test_case"
|
|
2
|
+
|
|
3
|
+
class Senren::<%= class_name %>Test < ApplicationSystemTestCase
|
|
4
|
+
test "<%= file_name %> connects its Stimulus controller" do
|
|
5
|
+
skip "Add a demo route or fixture page that renders Senren::<%= class_name %>Component, then assert behavior here."
|
|
6
|
+
end
|
|
7
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators/base'
|
|
4
|
+
require 'senren/rails'
|
|
5
|
+
|
|
6
|
+
module Senren
|
|
7
|
+
module Generators
|
|
8
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
|
|
11
|
+
class_option :force, type: :boolean, default: false,
|
|
12
|
+
desc: 'Overwrite existing Senren-managed files.'
|
|
13
|
+
|
|
14
|
+
def create_senren_dir
|
|
15
|
+
empty_directory '.senren'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_components_dir
|
|
19
|
+
empty_directory 'app/components/senren'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create_stimulus_dir
|
|
23
|
+
empty_directory 'app/javascript/controllers/senren'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def create_assets_dir
|
|
27
|
+
empty_directory 'app/assets/stylesheets'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_public_dir
|
|
31
|
+
empty_directory 'public'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def copy_base_files
|
|
35
|
+
template 'base_component.rb.tt', 'app/components/senren/base_component.rb'
|
|
36
|
+
template 'senren.css.tt', 'app/assets/stylesheets/senren.css'
|
|
37
|
+
template 'conventions.md.tt', '.senren/conventions.md'
|
|
38
|
+
template 'installed_components.yml.tt', '.senren/installed_components.yml'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def mirror_registry
|
|
42
|
+
copy_file Senren::Rails.registry_path, '.senren/registry.yml'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def write_skill_file
|
|
46
|
+
say_status :senren, 'writing .senren/skill.md'
|
|
47
|
+
Senren::Rails::SkillWriter.new(paths: host_paths).sync!
|
|
48
|
+
end
|
|
49
|
+
|
|
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!
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def print_next_steps
|
|
56
|
+
say "\nSenren installed."
|
|
57
|
+
say 'Next: bin/rails senren:add button card badge alert dialog'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def host_paths
|
|
63
|
+
@host_paths ||= Senren::Rails::HostPaths.new(destination_root)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Senren
|
|
2
|
+
# Base class for every Senren ViewComponent.
|
|
3
|
+
#
|
|
4
|
+
# Provides:
|
|
5
|
+
# - keyword-only constructor with `class_name:` (renamed from `class:`)
|
|
6
|
+
# - variant and size resolution against class-level constants
|
|
7
|
+
# - small class merger (no external `tailwind_merge` dependency in v0.1)
|
|
8
|
+
# - a default root-attribute helper that emits `data-senren-component="<name>"`
|
|
9
|
+
class BaseComponent < ViewComponent::Base
|
|
10
|
+
VARIANTS = { default: "" }.freeze
|
|
11
|
+
SIZES = { md: "" }.freeze
|
|
12
|
+
|
|
13
|
+
attr_reader :class_name, :variant, :size, :html_attrs
|
|
14
|
+
|
|
15
|
+
def initialize(variant: :default, size: :md, class_name: nil, **html_attrs)
|
|
16
|
+
@variant = resolve!(variant, self.class::VARIANTS, :variant)
|
|
17
|
+
@size = resolve!(size, self.class::SIZES, :size)
|
|
18
|
+
@class_name = class_name
|
|
19
|
+
@html_attrs = html_attrs
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Compose final root attributes; subclasses pass their base classes.
|
|
23
|
+
def root_attrs(*classes, **extra)
|
|
24
|
+
data = (extra.delete(:data) || {}).merge(senren_component: senren_component_name)
|
|
25
|
+
tag_class = merge_classes(classes, self.class::VARIANTS[@variant], self.class::SIZES[@size], @class_name, extra.delete(:class))
|
|
26
|
+
{ class: tag_class, data: data, **html_attrs, **extra }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def senren_component_name
|
|
30
|
+
self.class.name.to_s.sub(/^Senren::/, "").sub(/Component$/, "").gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def resolve!(value, table, label)
|
|
36
|
+
key = value.to_sym
|
|
37
|
+
return key if table.key?(key)
|
|
38
|
+
raise ArgumentError, "Unknown #{label}: #{value.inspect}. Allowed: #{table.keys.join(', ')}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def merge_classes(*sources)
|
|
42
|
+
sources.flatten.map { |s| s.to_s.strip }.reject(&:empty?).join(" ")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Senren Conventions
|
|
2
|
+
|
|
3
|
+
This file is for both human contributors and AI coding agents. Keep it short
|
|
4
|
+
and obey it strictly.
|
|
5
|
+
|
|
6
|
+
## Hard rules
|
|
7
|
+
|
|
8
|
+
1. Use Senren components before writing custom HTML for the same purpose.
|
|
9
|
+
2. Server-render via ViewComponent. Stimulus only for **local** behavior.
|
|
10
|
+
3. Turbo handles server state. Controllers do not fetch/XHR from JS.
|
|
11
|
+
4. No React, Vue, Alpine, lit, or external state framework. Ever.
|
|
12
|
+
5. Tailwind classes must use **semantic tokens** (`bg-background`,
|
|
13
|
+
`text-foreground`, `bg-primary`, `text-muted-foreground`,
|
|
14
|
+
`border-border`, `bg-destructive`). Do not hard-code `gray-*`,
|
|
15
|
+
`slate-*`, `zinc-*`, etc. in component templates.
|
|
16
|
+
6. Every component root carries `data-senren-component="<name>"` so DOM
|
|
17
|
+
inspection and tests can locate it.
|
|
18
|
+
7. **Render with parens when passing inline content blocks**. Ruby block
|
|
19
|
+
precedence makes `render Senren::Foo.new(args) { "bar" }` attach the
|
|
20
|
+
block to `.new`, **not** to `render`, producing an empty component.
|
|
21
|
+
Use either of these forms instead:
|
|
22
|
+
```erb
|
|
23
|
+
<%= render(Senren::ButtonComponent.new(variant: :primary)) { "Save" } %>
|
|
24
|
+
|
|
25
|
+
<%= render Senren::ButtonComponent.new(variant: :primary) do %>
|
|
26
|
+
Save
|
|
27
|
+
<% end %>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## File ownership
|
|
31
|
+
|
|
32
|
+
| Path | Owned by |
|
|
33
|
+
| ----------------------------------- | ---------------- |
|
|
34
|
+
| `app/components/senren/**` | This app |
|
|
35
|
+
| `app/javascript/controllers/senren/**` | This app |
|
|
36
|
+
| `app/assets/stylesheets/senren.css` | This app |
|
|
37
|
+
| `.senren/skill.md` | Generated region only (between markers) |
|
|
38
|
+
| `.senren/registry.yml` | Generator (mirror of gem registry) |
|
|
39
|
+
| `.senren/installed_components.yml` | Generator (ledger) |
|
|
40
|
+
| `.senren/conventions.md` | This file - safe to edit |
|
|
41
|
+
| `public/llms.txt` | Generator (do not edit) |
|
|
42
|
+
| `public/llms-full.txt` | Generator (do not edit) |
|
|
43
|
+
|
|
44
|
+
## Adding a component
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
bin/rails senren:add <name> [<name>...]
|
|
48
|
+
bin/rails senren:add dialog --no-client # override registry default
|
|
49
|
+
bin/rails senren:add button --client # override registry default
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
After install you can edit any file under `app/components/senren/` directly.
|
|
53
|
+
Senren never overwrites those files unless you pass `--force`.
|
|
54
|
+
|
|
55
|
+
## Skill file maintenance
|
|
56
|
+
|
|
57
|
+
`.senren/skill.md` has a generated region delimited by:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
<!-- senren:skill:start -->
|
|
61
|
+
...generated...
|
|
62
|
+
<!-- senren:skill:end -->
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
You can write app-specific notes outside those markers. They are preserved
|
|
66
|
+
across `bin/rails senren:skill:sync`.
|