rails-design-profiles 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f94b1e0af567ce077819a716dc34eb34462542a7bf251cdbb287376fcc7ee3e6
4
+ data.tar.gz: 9c96463447b584330e2e09d95da375b5c96ddc3fad795536a936feb2d584c5c0
5
+ SHA512:
6
+ metadata.gz: 14674431eac89956035122bc7a4a1941258ca2248b76d2d9bc173df3592defd327c722f7ae517c101cbf5a6f954f9072f2601f9a4fffcc3c9ae97c69741a06d6
7
+ data.tar.gz: ca65b17b242bc0805a8dda7f69716ecbb85d30b17e2515ade2ea9223ccc753d844552e0c64103569ee32d8b50c5ec594f83cd4429ae721cd9c729d24bc497a2c
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1 - 2026-07-17
4
+
5
+ - Refresh project documentation, community materials, GitHub Pages, and CI compatibility.
6
+
7
+ ## 0.1.0 - 2026-07-16
8
+
9
+ - Initial release with Rails helper, profile configuration, generators, and public reference tasks.
@@ -0,0 +1,4 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ Contributors are expected to be respectful, constructive, and inclusive. Harassment or discriminatory behavior is not tolerated. Report concerns privately to fnpaladini@gmail.com.
4
+
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,23 @@
1
+ # Contributing
2
+
3
+ Thank you for helping make Rails interfaces more intentional and more maintainable.
4
+
5
+ ## Before you start
6
+
7
+ - Use [Discussions](https://github.com/paladini/rails-design-profiles/discussions) for questions and proposals; open an issue when you have a reproducible bug or a focused feature.
8
+ - Keep pull requests narrow. Discuss changes that alter public configuration, task names, or output first.
9
+ - Never submit copied third-party CSS, assets, logos, or proprietary `DESIGN.md` content. References are context; executable tokens are authored and reviewed by the application team.
10
+
11
+ ## Local workflow
12
+
13
+ ```sh
14
+ bundle install
15
+ bundle exec rake test
16
+ gem build rails-design-profiles.gemspec
17
+ ```
18
+
19
+ The CI suite verifies Ruby 3.1 with Rails 7.1 and current supported Ruby versions. Add a regression test for each behavior change and update the README when a public command, configuration key, or output changes.
20
+
21
+ ## Pull requests
22
+
23
+ Explain the user problem, the chosen behavior, and how you verified it. Maintainers prioritize correctness, small APIs, accessible output, and changes that preserve the distinction between a design reference and a production theme.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fernando Paladini
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.
22
+
data/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # rails-design-profiles
2
+
3
+ [![CI](https://github.com/paladini/rails-design-profiles/actions/workflows/ci.yml/badge.svg)](https://github.com/paladini/rails-design-profiles/actions/workflows/ci.yml)
4
+ [![RubyGems](https://img.shields.io/gem/v/rails-design-profiles?logo=rubygems&label=RubyGems)](https://rubygems.org/gems/rails-design-profiles)
5
+ [![License](https://img.shields.io/github/license/paladini/rails-design-profiles)](LICENSE.txt)
6
+
7
+ **A design reference should influence your Rails UI without becoming an unreviewable theme generator.**
8
+
9
+ `rails-design-profiles` keeps the two jobs separate:
10
+
11
+ 1. `DESIGN.md` gives your AI agent a clear visual reference.
12
+ 2. A small YAML profile exposes the tokens your Rails application actually uses.
13
+
14
+ The result is a deliberate CSS-variable contract you can review, version, switch, and evolve with your team.
15
+
16
+ ## Why this exists
17
+
18
+ AI can read a `DESIGN.md`, but Markdown alone does not change an application. Conversely, replacing an entire stylesheet to try a visual direction is risky and hard to review.
19
+
20
+ This gem gives Rails applications a narrow bridge between the two: references stay local and explicit; CSS variables are emitted from an active profile; your components decide which tokens to consume.
21
+
22
+ ## Install
23
+
24
+ ```ruby
25
+ # Gemfile
26
+ gem "rails-design-profiles"
27
+ ```
28
+
29
+ ```sh
30
+ bundle install
31
+ bin/rails generate rails_design_profiles:install
32
+ ```
33
+
34
+ The generator adds a profile file and inserts the helper into `app/views/layouts/application.html.erb`:
35
+
36
+ ```erb
37
+ <%= rails_design_profiles_tags %>
38
+ ```
39
+
40
+ Use the resulting variables in your own CSS:
41
+
42
+ ```css
43
+ body {
44
+ color: var(--rdp-color-text);
45
+ background: var(--rdp-color-surface);
46
+ font-family: var(--rdp-font-body);
47
+ }
48
+
49
+ .button {
50
+ background: var(--rdp-color-primary);
51
+ padding: var(--rdp-space-page);
52
+ }
53
+ ```
54
+
55
+ ## The profile contract
56
+
57
+ `config/design_profiles.yml` is the source of truth for production tokens. Token names become `--rdp-*` CSS variables.
58
+
59
+ ```yaml
60
+ active: editorial
61
+ profiles:
62
+ editorial:
63
+ reference: config/design_profiles/references/claude.md
64
+ tokens:
65
+ color-primary: "#c15f3c"
66
+ color-surface: "#f8f6f2"
67
+ color-text: "#24211e"
68
+ font-body: "Newsreader, Georgia, serif"
69
+ space-page: "1.5rem"
70
+ ```
71
+
72
+ The active profile is global to the app and is resolved for every response. Version 0.1 does not include per-session or query-string overrides.
73
+
74
+ ## Bring a public reference into your project
75
+
76
+ The v0.1 catalog uses only the MIT-licensed [VoltAgent/awesome-design-md](https://github.com/VoltAgent/awesome-design-md) collection.
77
+
78
+ ```sh
79
+ bin/rails design:list
80
+ bin/rails design:install[claude]
81
+ ```
82
+
83
+ Installation stores the reference in `config/design_profiles/references/claude.md` and creates an empty `claude` profile. Read that reference with your agent, choose the tokens that fit your product, and add them deliberately to YAML.
84
+
85
+ Switch profiles with:
86
+
87
+ ```sh
88
+ bin/rails design:activate[claude]
89
+ ```
90
+
91
+ The gem never copies third-party CSS, assets, or branding. It is not affiliated with getdesign.md, VoltAgent, or any referenced brand.
92
+
93
+ ## How it works
94
+
95
+ ```text
96
+ DESIGN.md reference ──> team / AI design decisions ──> design_profiles.yml
97
+
98
+
99
+ <style> --rdp-* variables
100
+
101
+
102
+ your Rails components
103
+ ```
104
+
105
+ This is intentionally not a Markdown-to-CSS compiler. It preserves human judgment at the point where visual decisions become production code.
106
+
107
+ ## Compatibility
108
+
109
+ - Ruby 3.1+
110
+ - Rails 7.1+
111
+ - Asset pipeline, Propshaft, Importmap, Tailwind, and custom CSS all work because the output is standard CSS variables.
112
+
113
+ ## Community
114
+
115
+ - Ask questions and share workflows in [Discussions](https://github.com/paladini/rails-design-profiles/discussions).
116
+ - Report reproducible defects or focused feature requests through the issue templates.
117
+ - Read [CONTRIBUTING.md](CONTRIBUTING.md), [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), [SUPPORT.md](SUPPORT.md), and [SECURITY.md](SECURITY.md) before participating.
118
+
119
+ ### Roadmap
120
+
121
+ - [ ] Profile validation and diagnostics
122
+ - [ ] A profile preview surface for development
123
+ - [ ] More import adapters for openly licensed design references
124
+ - [ ] Community-maintained example Rails applications
125
+
126
+ ## Development
127
+
128
+ ```sh
129
+ bundle install
130
+ bundle exec rake test
131
+ gem build rails-design-profiles.gemspec
132
+ ```
133
+
134
+ ## License
135
+
136
+ MIT. See [LICENSE.txt](LICENSE.txt).
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RailsDesignProfiles
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def create_configuration
11
+ template "design_profiles.yml", "config/design_profiles.yml" unless File.exist?(path("config/design_profiles.yml"))
12
+ end
13
+
14
+ def add_style_helper_to_layout
15
+ layout = "app/views/layouts/application.html.erb"
16
+ return unless File.exist?(path(layout))
17
+ return if File.read(path(layout)).include?("rails_design_profiles_tags")
18
+
19
+ inject_into_file layout, " <%= rails_design_profiles_tags %>\n", before: " </head>\n"
20
+ end
21
+
22
+ private
23
+
24
+ def path(relative_path)
25
+ File.join(destination_root, relative_path)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,12 @@
1
+ # CSS values are intentionally explicit and reviewable. Reference files are context for AI agents.
2
+ active: default
3
+ profiles:
4
+ default:
5
+ reference: null
6
+ tokens:
7
+ color-primary: "#2563eb"
8
+ color-surface: "#ffffff"
9
+ color-text: "#111827"
10
+ font-body: "system-ui, sans-serif"
11
+ space-page: "1.5rem"
12
+
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+ require "uri"
6
+
7
+ module RailsDesignProfiles
8
+ class CatalogClient
9
+ REPOSITORY = "VoltAgent/awesome-design-md"
10
+ TREE_URL = "https://api.github.com/repos/#{REPOSITORY}/git/trees/main?recursive=1"
11
+ RAW_URL = "https://raw.githubusercontent.com/#{REPOSITORY}/main/design-md/%<slug>s/DESIGN.md"
12
+
13
+ def initialize(http_get: nil)
14
+ @http_get = http_get || method(:get)
15
+ end
16
+
17
+ def slugs
18
+ tree = JSON.parse(@http_get.call(TREE_URL)).fetch("tree")
19
+ tree.filter_map { |entry| entry.fetch("path")[%r{\Adesign-md/([^/]+)/DESIGN\.md\z}, 1] }.sort
20
+ rescue JSON::ParserError, KeyError => error
21
+ raise CatalogError, "Unexpected catalog response: #{error.message}"
22
+ end
23
+
24
+ def download(slug)
25
+ validate_slug!(slug)
26
+ @http_get.call(format(RAW_URL, slug: slug))
27
+ end
28
+
29
+ private
30
+
31
+ def get(url)
32
+ uri = URI(url)
33
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 5, read_timeout: 10) do |http|
34
+ http.get(uri.request_uri, "User-Agent" => "rails-design-profiles/#{VERSION}")
35
+ end
36
+ return response.body if response.is_a?(Net::HTTPSuccess)
37
+
38
+ raise CatalogError, "Catalog request failed (HTTP #{response.code})"
39
+ rescue SocketError, Timeout::Error, Errno::ECONNREFUSED => error
40
+ raise CatalogError, "Catalog request failed: #{error.message}"
41
+ end
42
+
43
+ def validate_slug!(slug)
44
+ return if slug.is_a?(String) && slug.match?(/\A[a-z0-9]+(?:-[a-z0-9]+)*\z/)
45
+
46
+ raise CatalogError, "Invalid catalog slug: #{slug.inspect}"
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDesignProfiles
4
+ module Helper
5
+ def rails_design_profiles_tags
6
+ css = RailsDesignProfiles.store.css_variables
7
+ return "".html_safe if css.empty?
8
+
9
+ content_tag(:style, ":root {#{css}}".html_safe, id: "rails-design-profiles")
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "yaml"
5
+
6
+ module RailsDesignProfiles
7
+ class ProfileStore
8
+ CONFIG_PATH = "config/design_profiles.yml"
9
+
10
+ def initialize(root)
11
+ @root = Pathname(root)
12
+ end
13
+
14
+ def active_profile
15
+ profile_named(data.fetch("active"))
16
+ rescue KeyError
17
+ raise ConfigurationError, "Missing active profile in #{config_path}"
18
+ end
19
+
20
+ def css_variables
21
+ tokens = active_profile.fetch("tokens", {})
22
+ unless tokens.is_a?(Hash)
23
+ raise ConfigurationError, "Profile tokens must be a mapping in #{config_path}"
24
+ end
25
+
26
+ tokens.filter_map do |name, value|
27
+ next unless valid_token?(name, value)
28
+
29
+ "--rdp-#{name}: #{value};"
30
+ end.join
31
+ end
32
+
33
+ def install_reference!(slug, contents)
34
+ validate_slug!(slug)
35
+ reference_path = references_path.join("#{slug}.md")
36
+ raise ConfigurationError, "Reference already exists: #{reference_path}" if reference_path.exist?
37
+
38
+ config = data
39
+ profiles = config.fetch("profiles")
40
+ if profiles.key?(slug)
41
+ raise ConfigurationError, "Profile already exists: #{slug}"
42
+ end
43
+
44
+ FileUtils.mkdir_p(reference_path.dirname)
45
+ reference_path.write(contents)
46
+ profiles[slug] = {
47
+ "reference" => reference_path.relative_path_from(@root).to_s,
48
+ "tokens" => {}
49
+ }
50
+ write(config)
51
+ rescue StandardError
52
+ FileUtils.rm_f(reference_path) if defined?(reference_path) && reference_path&.exist?
53
+ raise
54
+ end
55
+
56
+ def activate!(name)
57
+ config = data
58
+ profile_named(name, config)
59
+ config["active"] = name
60
+ write(config)
61
+ end
62
+
63
+ private
64
+
65
+ def data
66
+ raise ConfigurationError, "Missing configuration: #{config_path}" unless config_path.exist?
67
+
68
+ parsed = YAML.safe_load(config_path.read, permitted_classes: [], aliases: false)
69
+ unless parsed.is_a?(Hash) && parsed["profiles"].is_a?(Hash)
70
+ raise ConfigurationError, "Expected active and profiles mappings in #{config_path}"
71
+ end
72
+
73
+ parsed
74
+ rescue Psych::Exception => error
75
+ raise ConfigurationError, "Invalid YAML in #{config_path}: #{error.message}"
76
+ end
77
+
78
+ def write(config)
79
+ config_path.write(YAML.dump(config))
80
+ end
81
+
82
+ def profile_named(name, config = data)
83
+ profile = config.fetch("profiles")[name]
84
+ raise ProfileNotFound, "Unknown design profile: #{name}" unless profile.is_a?(Hash)
85
+
86
+ profile
87
+ end
88
+
89
+ def config_path
90
+ @root.join(CONFIG_PATH)
91
+ end
92
+
93
+ def references_path
94
+ @root.join("config/design_profiles/references")
95
+ end
96
+
97
+ def validate_slug!(slug)
98
+ return if slug.match?(/\A[a-z0-9]+(?:-[a-z0-9]+)*\z/)
99
+
100
+ raise ConfigurationError, "Invalid catalog slug: #{slug.inspect}"
101
+ end
102
+
103
+ def valid_token?(name, value)
104
+ name.is_a?(String) && value.is_a?(String) &&
105
+ name.match?(/\A[a-z0-9]+(?:-[a-z0-9]+)*\z/) &&
106
+ value.match?(/\A[a-zA-Z0-9#%.,()\s\/'\"+*_\-]+\z/)
107
+ end
108
+ end
109
+ end
110
+
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDesignProfiles
4
+ class Railtie < Rails::Railtie
5
+ initializer "rails_design_profiles.helper" do
6
+ ActiveSupport.on_load(:action_view) { include RailsDesignProfiles::Helper }
7
+ end
8
+
9
+ rake_tasks do
10
+ load File.expand_path("../tasks/rails_design_profiles_tasks.rake", __dir__)
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDesignProfiles
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "rails_design_profiles/version"
5
+ require "rails_design_profiles/profile_store"
6
+ require "rails_design_profiles/catalog_client"
7
+ require "rails_design_profiles/helper"
8
+ require "rails_design_profiles/railtie" if defined?(Rails::Railtie)
9
+
10
+ module RailsDesignProfiles
11
+ class Error < StandardError; end
12
+ class ConfigurationError < Error; end
13
+ class ProfileNotFound < Error; end
14
+ class CatalogError < Error; end
15
+
16
+ class << self
17
+ attr_writer :root
18
+
19
+ def root
20
+ return @root if @root
21
+ return Rails.root if defined?(Rails) && Rails.respond_to?(:root)
22
+
23
+ Pathname.pwd
24
+ end
25
+
26
+ def store
27
+ ProfileStore.new(root)
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :design do
4
+ desc "List public DESIGN.md references from VoltAgent/awesome-design-md"
5
+ task :list do
6
+ RailsDesignProfiles::CatalogClient.new.slugs.each { |slug| puts slug }
7
+ end
8
+
9
+ desc "Download a public DESIGN.md reference and create a blank token profile"
10
+ task :install, [:slug] => :environment do |_task, arguments|
11
+ slug = arguments.fetch(:slug) { abort "Usage: rails design:install[slug]" }
12
+ contents = RailsDesignProfiles::CatalogClient.new.download(slug)
13
+ RailsDesignProfiles.store.install_reference!(slug, contents)
14
+ puts "Installed #{slug}. Add tokens to config/design_profiles.yml before activating it."
15
+ rescue RailsDesignProfiles::Error => error
16
+ abort error.message
17
+ end
18
+
19
+ desc "Set the active design profile"
20
+ task :activate, [:profile] => :environment do |_task, arguments|
21
+ profile = arguments.fetch(:profile) { abort "Usage: rails design:activate[profile]" }
22
+ RailsDesignProfiles.store.activate!(profile)
23
+ puts "Active design profile: #{profile}"
24
+ rescue RailsDesignProfiles::Error => error
25
+ abort error.message
26
+ end
27
+ end
28
+
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-design-profiles
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Fernando Paladini
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: actionview
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: railties
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '7.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '7.1'
40
+ description: Keep DESIGN.md references alongside executable, reviewable CSS token
41
+ profiles in Rails.
42
+ email:
43
+ - fnpaladini@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG.md
49
+ - CODE_OF_CONDUCT.md
50
+ - CONTRIBUTING.md
51
+ - LICENSE.txt
52
+ - README.md
53
+ - lib/generators/rails_design_profiles/install/install_generator.rb
54
+ - lib/generators/rails_design_profiles/install/templates/design_profiles.yml
55
+ - lib/rails_design_profiles.rb
56
+ - lib/rails_design_profiles/catalog_client.rb
57
+ - lib/rails_design_profiles/helper.rb
58
+ - lib/rails_design_profiles/profile_store.rb
59
+ - lib/rails_design_profiles/railtie.rb
60
+ - lib/rails_design_profiles/version.rb
61
+ - lib/tasks/rails_design_profiles_tasks.rake
62
+ homepage: https://github.com/paladini/rails-design-profiles
63
+ licenses:
64
+ - MIT
65
+ metadata:
66
+ source_code_uri: https://github.com/paladini/rails-design-profiles
67
+ changelog_uri: https://github.com/paladini/rails-design-profiles/blob/main/CHANGELOG.md
68
+ rubygems_mfa_required: 'true'
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '3.1'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubygems_version: 4.0.16
84
+ specification_version: 4
85
+ summary: Versioned, CSS-variable design profiles for Rails applications.
86
+ test_files: []