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.
Files changed (182) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +33 -0
  3. data/CONTRIBUTING.md +63 -0
  4. data/LICENSE +21 -0
  5. data/README.md +135 -0
  6. data/Rakefile +22 -0
  7. data/docs/visual_style.md +51 -0
  8. data/lib/generators/senren/component/component_generator.rb +62 -0
  9. data/lib/generators/senren/component/templates/component.html.erb.tt +3 -0
  10. data/lib/generators/senren/component/templates/component.rb.tt +13 -0
  11. data/lib/generators/senren/component/templates/component_test.rb.tt +16 -0
  12. data/lib/generators/senren/component/templates/controller.js.tt +23 -0
  13. data/lib/generators/senren/component/templates/system_test.rb.tt +7 -0
  14. data/lib/generators/senren/install/install_generator.rb +67 -0
  15. data/lib/generators/senren/install/templates/base_component.rb.tt +45 -0
  16. data/lib/generators/senren/install/templates/conventions.md.tt +66 -0
  17. data/lib/generators/senren/install/templates/installed_components.yml.tt +4 -0
  18. data/lib/generators/senren/install/templates/senren.css.tt +164 -0
  19. data/lib/senren/rails/component_copier.rb +111 -0
  20. data/lib/senren/rails/doctor.rb +86 -0
  21. data/lib/senren/rails/engine.rb +16 -0
  22. data/lib/senren/rails/host_paths.rb +36 -0
  23. data/lib/senren/rails/installer.rb +83 -0
  24. data/lib/senren/rails/llms_writer.rb +149 -0
  25. data/lib/senren/rails/registry.rb +161 -0
  26. data/lib/senren/rails/skill_writer.rb +166 -0
  27. data/lib/senren/rails/version.rb +7 -0
  28. data/lib/senren/rails.rb +39 -0
  29. data/lib/tasks/senren.rake +74 -0
  30. data/registry/components.yml +1053 -0
  31. data/registry/groups.yml +25 -0
  32. data/registry/recipes.yml +79 -0
  33. data/templates/components/accordion/accordion_component.html.erb +16 -0
  34. data/templates/components/accordion/accordion_component.rb +31 -0
  35. data/templates/components/activity_feed/activity_feed_component.html.erb +22 -0
  36. data/templates/components/activity_feed/activity_feed_component.rb +19 -0
  37. data/templates/components/alert/alert_component.html.erb +9 -0
  38. data/templates/components/alert/alert_component.rb +18 -0
  39. data/templates/components/alert_dialog/alert_dialog_component.html.erb +34 -0
  40. data/templates/components/alert_dialog/alert_dialog_component.rb +21 -0
  41. data/templates/components/api_key_field/api_key_field_component.html.erb +13 -0
  42. data/templates/components/api_key_field/api_key_field_component.rb +20 -0
  43. data/templates/components/app_shell/app_shell_component.html.erb +28 -0
  44. data/templates/components/app_shell/app_shell_component.rb +24 -0
  45. data/templates/components/aspect_ratio/aspect_ratio_component.html.erb +3 -0
  46. data/templates/components/aspect_ratio/aspect_ratio_component.rb +14 -0
  47. data/templates/components/avatar/avatar_component.html.erb +27 -0
  48. data/templates/components/avatar/avatar_component.rb +30 -0
  49. data/templates/components/badge/badge_component.html.erb +1 -0
  50. data/templates/components/badge/badge_component.rb +16 -0
  51. data/templates/components/billing_plan_card/billing_plan_card_component.html.erb +28 -0
  52. data/templates/components/billing_plan_card/billing_plan_card_component.rb +27 -0
  53. data/templates/components/breadcrumb/breadcrumb_component.html.erb +23 -0
  54. data/templates/components/breadcrumb/breadcrumb_component.rb +30 -0
  55. data/templates/components/bulk_action_bar/bulk_action_bar_component.html.erb +12 -0
  56. data/templates/components/bulk_action_bar/bulk_action_bar_component.rb +24 -0
  57. data/templates/components/button/button_component.html.erb +6 -0
  58. data/templates/components/button/button_component.rb +29 -0
  59. data/templates/components/calendar/calendar_component.html.erb +21 -0
  60. data/templates/components/calendar/calendar_component.rb +30 -0
  61. data/templates/components/card/card_component.html.erb +13 -0
  62. data/templates/components/card/card_component.rb +17 -0
  63. data/templates/components/carousel/carousel_component.html.erb +68 -0
  64. data/templates/components/carousel/carousel_component.rb +34 -0
  65. data/templates/components/checkbox/checkbox_component.html.erb +8 -0
  66. data/templates/components/checkbox/checkbox_component.rb +19 -0
  67. data/templates/components/checkbox_group/checkbox_group_component.html.erb +10 -0
  68. data/templates/components/checkbox_group/checkbox_group_component.rb +30 -0
  69. data/templates/components/clipboard/clipboard_component.html.erb +7 -0
  70. data/templates/components/clipboard/clipboard_component.rb +17 -0
  71. data/templates/components/codeblock/codeblock_component.html.erb +11 -0
  72. data/templates/components/codeblock/codeblock_component.rb +31 -0
  73. data/templates/components/collapsible/collapsible_component.html.erb +9 -0
  74. data/templates/components/collapsible/collapsible_component.rb +19 -0
  75. data/templates/components/combobox/combobox_component.html.erb +19 -0
  76. data/templates/components/combobox/combobox_component.rb +38 -0
  77. data/templates/components/command/command_component.html.erb +22 -0
  78. data/templates/components/command/command_component.rb +38 -0
  79. data/templates/components/context_menu/context_menu_component.html.erb +11 -0
  80. data/templates/components/context_menu/context_menu_component.rb +11 -0
  81. data/templates/components/data_table/data_table_component.html.erb +50 -0
  82. data/templates/components/data_table/data_table_component.rb +42 -0
  83. data/templates/components/date_picker/date_picker_component.html.erb +5 -0
  84. data/templates/components/date_picker/date_picker_component.rb +21 -0
  85. data/templates/components/dialog/dialog_component.html.erb +38 -0
  86. data/templates/components/dialog/dialog_component.rb +22 -0
  87. data/templates/components/dropdown_menu/dropdown_menu_component.html.erb +12 -0
  88. data/templates/components/dropdown_menu/dropdown_menu_component.rb +36 -0
  89. data/templates/components/empty_state/empty_state_component.html.erb +18 -0
  90. data/templates/components/empty_state/empty_state_component.rb +22 -0
  91. data/templates/components/filter_bar/filter_bar_component.html.erb +5 -0
  92. data/templates/components/filter_bar/filter_bar_component.rb +15 -0
  93. data/templates/components/form/form_component.html.erb +3 -0
  94. data/templates/components/form/form_component.rb +18 -0
  95. data/templates/components/hover_card/hover_card_component.html.erb +10 -0
  96. data/templates/components/hover_card/hover_card_component.rb +11 -0
  97. data/templates/components/input/input_component.html.erb +1 -0
  98. data/templates/components/input/input_component.rb +28 -0
  99. data/templates/components/invite_member_dialog/invite_member_dialog_component.html.erb +35 -0
  100. data/templates/components/invite_member_dialog/invite_member_dialog_component.rb +26 -0
  101. data/templates/components/label/label_component.html.erb +4 -0
  102. data/templates/components/label/label_component.rb +19 -0
  103. data/templates/components/link/link_component.html.erb +1 -0
  104. data/templates/components/link/link_component.rb +25 -0
  105. data/templates/components/masked_input/masked_input_component.html.erb +1 -0
  106. data/templates/components/masked_input/masked_input_component.rb +18 -0
  107. data/templates/components/native_select/native_select_component.html.erb +14 -0
  108. data/templates/components/native_select/native_select_component.rb +52 -0
  109. data/templates/components/page_header/page_header_component.html.erb +20 -0
  110. data/templates/components/page_header/page_header_component.rb +19 -0
  111. data/templates/components/pagination/pagination_component.html.erb +11 -0
  112. data/templates/components/pagination/pagination_component.rb +24 -0
  113. data/templates/components/popover/popover_component.html.erb +9 -0
  114. data/templates/components/popover/popover_component.rb +11 -0
  115. data/templates/components/progress/progress_component.html.erb +11 -0
  116. data/templates/components/progress/progress_component.rb +26 -0
  117. data/templates/components/radio_button/radio_button_component.html.erb +8 -0
  118. data/templates/components/radio_button/radio_button_component.rb +19 -0
  119. data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.html.erb +32 -0
  120. data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.rb +30 -0
  121. data/templates/components/search_input/search_input_component.html.erb +14 -0
  122. data/templates/components/search_input/search_input_component.rb +18 -0
  123. data/templates/components/select/select_component.html.erb +1 -0
  124. data/templates/components/select/select_component.rb +19 -0
  125. data/templates/components/separator/separator_component.html.erb +1 -0
  126. data/templates/components/separator/separator_component.rb +12 -0
  127. data/templates/components/settings_section/settings_section_component.html.erb +20 -0
  128. data/templates/components/settings_section/settings_section_component.rb +18 -0
  129. data/templates/components/sheet/sheet_component.html.erb +37 -0
  130. data/templates/components/sheet/sheet_component.rb +27 -0
  131. data/templates/components/shortcut_key/shortcut_key_component.html.erb +6 -0
  132. data/templates/components/shortcut_key/shortcut_key_component.rb +15 -0
  133. data/templates/components/sidebar/sidebar_component.html.erb +14 -0
  134. data/templates/components/sidebar/sidebar_component.rb +37 -0
  135. data/templates/components/skeleton/skeleton_component.html.erb +1 -0
  136. data/templates/components/skeleton/skeleton_component.rb +13 -0
  137. data/templates/components/stat_card/stat_card_component.html.erb +20 -0
  138. data/templates/components/stat_card/stat_card_component.rb +31 -0
  139. data/templates/components/switch/switch_component.html.erb +11 -0
  140. data/templates/components/switch/switch_component.rb +19 -0
  141. data/templates/components/table/table_component.html.erb +26 -0
  142. data/templates/components/table/table_component.rb +35 -0
  143. data/templates/components/tabs/tabs_component.html.erb +18 -0
  144. data/templates/components/tabs/tabs_component.rb +35 -0
  145. data/templates/components/team_member_list/team_member_list_component.html.erb +22 -0
  146. data/templates/components/team_member_list/team_member_list_component.rb +26 -0
  147. data/templates/components/textarea/textarea_component.html.erb +1 -0
  148. data/templates/components/textarea/textarea_component.rb +23 -0
  149. data/templates/components/theme_toggle/theme_toggle_component.html.erb +4 -0
  150. data/templates/components/theme_toggle/theme_toggle_component.rb +15 -0
  151. data/templates/components/tooltip/tooltip_component.html.erb +9 -0
  152. data/templates/components/tooltip/tooltip_component.rb +16 -0
  153. data/templates/components/top_nav/top_nav_component.html.erb +21 -0
  154. data/templates/components/top_nav/top_nav_component.rb +44 -0
  155. data/templates/components/typography/typography_component.html.erb +1 -0
  156. data/templates/components/typography/typography_component.rb +24 -0
  157. data/templates/controllers/accordion_controller.js +27 -0
  158. data/templates/controllers/alert_dialog_controller.js +38 -0
  159. data/templates/controllers/api_key_field_controller.js +36 -0
  160. data/templates/controllers/calendar_controller.js +16 -0
  161. data/templates/controllers/carousel_controller.js +50 -0
  162. data/templates/controllers/clipboard_controller.js +17 -0
  163. data/templates/controllers/collapsible_controller.js +13 -0
  164. data/templates/controllers/combobox_controller.js +64 -0
  165. data/templates/controllers/command_controller.js +80 -0
  166. data/templates/controllers/context_menu_controller.js +36 -0
  167. data/templates/controllers/data_table_controller.js +34 -0
  168. data/templates/controllers/date_picker_controller.js +17 -0
  169. data/templates/controllers/dialog_controller.js +50 -0
  170. data/templates/controllers/dropdown_menu_controller.js +92 -0
  171. data/templates/controllers/hover_card_controller.js +17 -0
  172. data/templates/controllers/invite_member_dialog_controller.js +28 -0
  173. data/templates/controllers/masked_input_controller.js +30 -0
  174. data/templates/controllers/popover_controller.js +42 -0
  175. data/templates/controllers/rich_text_editor_lite_controller.js +443 -0
  176. data/templates/controllers/select_controller.js +10 -0
  177. data/templates/controllers/sheet_controller.js +34 -0
  178. data/templates/controllers/sidebar_controller.js +10 -0
  179. data/templates/controllers/tabs_controller.js +41 -0
  180. data/templates/controllers/theme_toggle_controller.js +24 -0
  181. data/templates/controllers/tooltip_controller.js +10 -0
  182. 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,3 @@
1
+ <div <%%= tag.attributes(**root_attrs("rounded-md border border-border bg-background p-4 text-foreground"<% if options[:client] %>, data: { controller: "<%= stimulus_identifier %>" }<% end %>)) %>>
2
+ <%%= content %>
3
+ </div>
@@ -0,0 +1,13 @@
1
+ module Senren
2
+ class <%= class_name %>Component < BaseComponent
3
+ VARIANTS = {
4
+ default: ""
5
+ }.freeze
6
+
7
+ SIZES = {
8
+ sm: "",
9
+ md: "",
10
+ lg: ""
11
+ }.freeze
12
+ end
13
+ 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`.
@@ -0,0 +1,4 @@
1
+ # This file is the ledger of Senren components installed in this app.
2
+ # It is updated by `bin/rails senren:add` and `bin/rails senren:install`.
3
+ # Do not edit by hand unless you know what you are doing.
4
+ installed: []