plutonium 0.26.11 → 0.27.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e10062c979c0a1928e52aa93f22693aae2eb398cb5435301883b0de4212d12b7
4
- data.tar.gz: c72b315aa37c784b7d9502ba084a080b71b06e02cdeb9a9bdf5555b921e2b89c
3
+ metadata.gz: 429c40389e06fd8b0dd3a490515561c4819adda14e3837107426eb74101880d4
4
+ data.tar.gz: 7f11c16faa7b0e0972007afdced5713d13e4cb2a48397632938f19a56c1f6d90
5
5
  SHA512:
6
- metadata.gz: ad63980d8dafd43aaea7ecd69434aba8667f1b33a6b28cd753c37bb7bdf3b7607b4c182a0bcc1d4b7cb123eb245ae76b844c2f498d4eaa4ec40609324bac2760
7
- data.tar.gz: 73a2186b8e61124260b3eefd856a7a93f0cf53069221d22bd39022e8375b6fb0a108a01fc2226694da3b7fad0fd5a0ed7cd29a943c20e55586ef216c6f097ad9
6
+ metadata.gz: 8a74e71f54e4d3bd8bde88cb9c02c7f80bba61d5ed01ca20d62aa4559537073c35aeb94b1d9dcab3639b691279d8d8c7a88b1ae2e1da2a7ec26226e1835f251c
7
+ data.tar.gz: 44aa9125097ade4af6efda8f214bf9ba31cb59792bfa6660e303bb558bfa6da469921aa4954796ee948ca8403f471de3ec013de49abb46f6c83dd6a65b2e3f10
data/.cliff.toml ADDED
@@ -0,0 +1,68 @@
1
+ # git-cliff configuration file
2
+ # https://git-cliff.org/docs/configuration
3
+
4
+ [changelog]
5
+ # changelog header
6
+ header = """
7
+ # Changelog\n
8
+ All notable changes to this project will be documented in this file.\n
9
+ """
10
+ # template for the changelog body
11
+ body = """
12
+ {% if version %}\
13
+ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
14
+ {% else %}\
15
+ ## [Unreleased]
16
+ {% endif %}\
17
+ {% for group, commits in commits | group_by(attribute="group") %}
18
+ ### {{ group | upper_first }}
19
+ {% for commit in commits %}
20
+ - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
21
+ {% endfor %}
22
+ {% endfor %}\n
23
+ """
24
+ # remove the leading and trailing whitespace from the template
25
+ trim = true
26
+ # changelog footer
27
+ footer = """
28
+ <!-- generated by git-cliff -->
29
+ """
30
+
31
+ [git]
32
+ # parse the commits based on https://www.conventionalcommits.org
33
+ conventional_commits = true
34
+ # filter out the commits that are not conventional
35
+ filter_unconventional = true
36
+ # process each line of a commit as an individual commit
37
+ split_commits = false
38
+ # regex for preprocessing the commit messages
39
+ commit_preprocessors = [
40
+ { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/radioactive-labs/plutonium-core/issues/${2}))" },
41
+ ]
42
+ # regex for parsing and grouping commits
43
+ commit_parsers = [
44
+ { message = "^feat", group = "Features" },
45
+ { message = "^fix", group = "Bug Fixes" },
46
+ { message = "^doc", group = "Documentation" },
47
+ { message = "^perf", group = "Performance" },
48
+ { message = "^refactor", group = "Refactoring" },
49
+ { message = "^style", group = "Styling" },
50
+ { message = "^test", group = "Testing" },
51
+ { message = "^chore\\(release\\): prepare for", skip = true },
52
+ { message = "^chore", group = "Miscellaneous Tasks" },
53
+ { body = ".*security", group = "Security" },
54
+ ]
55
+ # protect breaking changes from being skipped due to matching a skipping commit_parser
56
+ protect_breaking_commits = false
57
+ # filter out the commits that are not matched by commit parsers
58
+ filter_commits = false
59
+ # glob pattern for matching git tags
60
+ tag_pattern = "v[0-9]*"
61
+ # regex for skipping tags
62
+ skip_tags = "v0.1.0-beta.1"
63
+ # regex for ignoring tags
64
+ ignore_tags = ""
65
+ # sort the tags topologically
66
+ topo_order = false
67
+ # sort the commits inside sections by oldest/newest order
68
+ sort_commits = "oldest"
data/CHANGELOG.md CHANGED
@@ -1,5 +1,70 @@
1
- ## [Unreleased]
1
+ ## [0.27.0] - 2025-11-05
2
2
 
3
- ## [0.1.0] - 2024-01-01
3
+ ### šŸš€ Features
4
4
 
5
- - Initial release
5
+ - Add field-level options support for input/display/column definitions
6
+
7
+ ### šŸ› Bug Fixes
8
+
9
+ - Reload version constant in release:publish task
10
+
11
+ ### šŸ“š Documentation
12
+
13
+ - Setup contribution guidelines using Conventional Commits
14
+ ## [0.26.9] - 2025-09-25
15
+
16
+ ### šŸš€ Features
17
+
18
+ - Disable csrf protection if authorization header is set
19
+ ## [0.26.8] - 2025-08-11
20
+
21
+ ### šŸ› Bug Fixes
22
+
23
+ - Prevent SQLite adapter error when using non-SQLite databases (#42)
24
+ - Fix STI model routing logic in controller (#43)
25
+ ## [0.26.6] - 2025-08-03
26
+
27
+ ### 🚜 Refactor
28
+
29
+ - Enhance color mode selector and integrate into header layout (#41)
30
+ ## [0.26.2] - 2025-07-22
31
+
32
+ ### šŸ› Bug Fixes
33
+
34
+ - Handle redirect after interaction submission (#37)
35
+ - Enhance flatpickr to attach to modals correctly (#35)
36
+ ## [0.23.2] - 2025-05-27
37
+
38
+ ### 🚜 Refactor
39
+
40
+ - Update EasyMDE styles (#33)
41
+ ## [0.23.1] - 2025-05-27
42
+
43
+ ### šŸ› Bug Fixes
44
+
45
+ - Hide password visibility checkbox when not needed (#32)
46
+ ## [0.21.1] - 2025-04-27
47
+
48
+ ### šŸš€ Features
49
+
50
+ - Preserve whitespace in hints to allow some formatting (#25)
51
+
52
+ ### 🚜 Refactor
53
+
54
+ - Fix join condition for `has_one` and `has_many` associations (#23)
55
+ ## [0.21.0] - 2025-04-01
56
+
57
+ ### šŸš€ Features
58
+
59
+ - Add cleaner cards for resources on the dashboard index page (#22)
60
+ ## [0.20.4] - 2025-03-15
61
+
62
+ ### šŸ› Bug Fixes
63
+
64
+ - Fix Tailwind CSS v4 upgrade issue for existing projects (#18)
65
+ ## [0.19.13] - 2025-03-02
66
+
67
+ ### šŸš€ Features
68
+
69
+ - Add password visibility toggle to sign up and login forms (#17)
70
+ ## [0.6.2] - 2024-02-21
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,134 @@
1
+ # Contributing to Plutonium
2
+
3
+ ## Commit Message Convention
4
+
5
+ This project follows [Conventional Commits](https://www.conventionalcommits.org/) for automated changelog generation and versioning.
6
+
7
+ ### Format
8
+
9
+ ```
10
+ <type>[optional scope]: <description>
11
+
12
+ [optional body]
13
+
14
+ [optional footer(s)]
15
+ ```
16
+
17
+ ### Types
18
+
19
+ - **feat**: A new feature (triggers MINOR version bump)
20
+ - **fix**: A bug fix (triggers PATCH version bump)
21
+ - **docs**: Documentation only changes
22
+ - **style**: Changes that don't affect code meaning (formatting, etc)
23
+ - **refactor**: Code change that neither fixes a bug nor adds a feature
24
+ - **perf**: Performance improvement
25
+ - **test**: Adding or updating tests
26
+ - **chore**: Maintenance tasks (dependencies, tooling, etc)
27
+
28
+ ### Breaking Changes
29
+
30
+ Add `BREAKING CHANGE:` in the footer or `!` after type to trigger a MAJOR version bump:
31
+
32
+ ```
33
+ feat!: remove deprecated API
34
+
35
+ BREAKING CHANGE: The old API has been removed. Use new API instead.
36
+ ```
37
+
38
+ ### Examples
39
+
40
+ ```bash
41
+ # Feature (bumps 0.26.11 -> 0.27.0)
42
+ git commit -m "feat: add field-level options support for input definitions"
43
+
44
+ # Bug fix (bumps 0.26.11 -> 0.26.12)
45
+ git commit -m "fix: resolve inheritance issue with controller_for"
46
+
47
+ # Breaking change (bumps 0.26.11 -> 1.0.0)
48
+ git commit -m "feat!: redesign definition DSL
49
+
50
+ BREAKING CHANGE: The definition DSL has been completely redesigned.
51
+ See migration guide for details."
52
+
53
+ # With scope
54
+ git commit -m "feat(ui): add new table component"
55
+ git commit -m "fix(forms): correct hint display on validation errors"
56
+
57
+ # Documentation
58
+ git commit -m "docs: update definition structure guide"
59
+ ```
60
+
61
+ ## Release Process
62
+
63
+ ### Option 1: Automated (Recommended)
64
+
65
+ ```bash
66
+ # See what the next version should be
67
+ rake release:next_version
68
+
69
+ # Prepare a new release (updates version, generates changelog)
70
+ rake release:prepare[0.27.0]
71
+
72
+ # Review changes
73
+ git diff
74
+
75
+ # Full automated release (prepare, commit, tag, push, publish)
76
+ rake release:full[0.27.0]
77
+ ```
78
+
79
+ ### Option 2: Manual
80
+
81
+ ```bash
82
+ # 1. Update version in lib/plutonium/version.rb
83
+ # 2. Generate changelog
84
+ git-cliff --tag v0.27.0 -o CHANGELOG.md
85
+
86
+ # 3. Commit and tag
87
+ git add -A
88
+ git commit -m "chore(release): prepare for v0.27.0"
89
+ git tag v0.27.0
90
+ git push origin main --tags
91
+
92
+ # 4. GitHub Actions will automatically publish to RubyGems
93
+ ```
94
+
95
+ ## Version Bumping Rules
96
+
97
+ Following semantic versioning:
98
+
99
+ - **MAJOR** (X.0.0): Breaking changes
100
+ - **MINOR** (0.X.0): New features (backwards compatible)
101
+ - **PATCH** (0.0.X): Bug fixes (backwards compatible)
102
+
103
+ The automation determines the version bump based on commits since the last tag:
104
+ - Any commit with `BREAKING CHANGE:` or `!` after type → MAJOR
105
+ - Any `feat:` commits → MINOR
106
+ - Any `fix:` commits → PATCH
107
+
108
+ ## Development Setup
109
+
110
+ ```bash
111
+ # Install dependencies
112
+ bundle install
113
+
114
+ # Run tests
115
+ bundle exec rspec
116
+
117
+ # Install git-cliff for changelog generation (optional)
118
+ brew install git-cliff # macOS
119
+ # or
120
+ cargo install git-cliff # via Rust
121
+ ```
122
+
123
+ ## Pull Request Process
124
+
125
+ 1. Fork the repository
126
+ 2. Create a feature branch: `git checkout -b feat/my-feature`
127
+ 3. Make your changes with conventional commits
128
+ 4. Run tests: `bundle exec rspec`
129
+ 5. Push and create a pull request
130
+ 6. The PR title should also follow conventional commit format
131
+
132
+ ## Questions?
133
+
134
+ Open an issue or discussion on GitHub!
data/Rakefile CHANGED
@@ -2,6 +2,9 @@ require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
  require "standard/rake"
4
4
 
5
+ # Load custom rake tasks
6
+ Dir.glob("lib/tasks/**/*.rake").each { |r| load r }
7
+
5
8
  task default: %i[test standard]
6
9
 
7
10
  task :assets do
@@ -143,17 +143,35 @@ end
143
143
  ### 2. **Add Custom Options**
144
144
  ```ruby
145
145
  class PostDefinition < Plutonium::Resource::Definition
146
- # Add placeholder text
147
- input :title, placeholder: "Enter an engaging title"
148
-
149
- # Add custom CSS classes
146
+ # Field-level options for inputs (affect the field wrapper)
147
+ input :title,
148
+ label: "Article Title",
149
+ hint: "Enter a descriptive title",
150
+ placeholder: "e.g. My First Post"
151
+
152
+ # Field-level options for displays (affect the field wrapper)
153
+ display :content,
154
+ label: "Article Content",
155
+ description: "Published article content",
156
+ placeholder: "No content available"
157
+
158
+ # Tag-level options (passed to the HTML element)
159
+ input :slug, class: "font-mono"
150
160
  display :title, class: "text-2xl font-bold"
151
161
 
152
- # Add wrapper styling
162
+ # Wrapper styling
153
163
  display :content, wrapper: {class: "prose max-w-none"}
154
164
  end
155
165
  ```
156
166
 
167
+ ::: tip Field-level vs Tag-level Options
168
+ Plutonium distinguishes between two types of options:
169
+ - **Field-level options** (`label`, `hint`, `description`, `placeholder`) - Processed by the field wrapper, affect labels and help text
170
+ - **Tag-level options** (`class`, `data`, `required`, etc.) - Passed directly to the HTML element
171
+
172
+ The framework automatically routes each option to the correct destination.
173
+ :::
174
+
157
175
  ### 3. **Configure Select Options**
158
176
  ```ruby
159
177
  class PostDefinition < Plutonium::Resource::Definition
@@ -794,6 +812,308 @@ end
794
812
 
795
813
  The Definition module provides a clean, declarative way to configure resource behavior while maintaining clear separation between configuration (definitions), authorization (policies), and business logic (interactions).
796
814
 
815
+ ## Structuring Your Definition File
816
+
817
+ A well-structured definition file is easy to read, maintain, and understand. Here's the recommended organization:
818
+
819
+ ### 1. Logical Grouping
820
+
821
+ Group related declarations together for better readability:
822
+
823
+ ```ruby
824
+ class PostDefinition < Plutonium::Resource::Definition
825
+ # === Field Type Overrides ===
826
+ # Group all basic field type changes together
827
+ field :content, as: :rich_text
828
+ field :author_id, as: :hidden
829
+ field :metadata, as: :json
830
+
831
+ # === Input Customizations ===
832
+ # Group form-specific configurations
833
+ input :title,
834
+ label: "Post Title",
835
+ hint: "Enter a descriptive title",
836
+ placeholder: "e.g. Getting Started with Plutonium"
837
+
838
+ input :slug,
839
+ label: "URL Slug",
840
+ hint: "Leave blank to auto-generate from title",
841
+ class: "font-mono"
842
+
843
+ input :category, as: :select, choices: %w[Tech Business Lifestyle]
844
+
845
+ # === Display Customizations ===
846
+ # Group show page configurations
847
+ display :content, as: :markdown
848
+ display :view_count, label: "Views", class: "font-bold"
849
+ display :metadata, wrapper: {class: "col-span-full"}
850
+
851
+ # === Column Customizations ===
852
+ # Group table-specific configurations
853
+ column :title, label: "Article Title"
854
+ column :view_count, align: :end, label: "Views"
855
+ column :published_at, align: :end
856
+
857
+ # === Search & Filtering ===
858
+ # Group query-related configurations
859
+ search do |scope, query|
860
+ scope.where("title ILIKE ? OR content ILIKE ?", "%#{query}%", "%#{query}%")
861
+ end
862
+
863
+ filter :status, as: :select, choices: %w[draft published archived]
864
+ filter :category, as: :select, choices: %w[Tech Business Lifestyle]
865
+
866
+ # === Scopes ===
867
+ scope :published
868
+ scope :draft
869
+ scope :archived
870
+
871
+ # === Sorting ===
872
+ sort :title
873
+ sort :created_at
874
+ sort :view_count
875
+
876
+ # === Actions ===
877
+ action :publish, interaction: Posts::Publish
878
+ action :archive, interaction: Posts::Archive
879
+ end
880
+ ```
881
+
882
+ ### 2. Use Comments to Delineate Sections
883
+
884
+ Add section headers with comments to make the file scannable:
885
+
886
+ ```ruby
887
+ class PostDefinition < Plutonium::Resource::Definition
888
+ # ========================================
889
+ # Field Configuration
890
+ # ========================================
891
+
892
+ field :content, as: :rich_text
893
+
894
+ # ========================================
895
+ # Form Inputs
896
+ # ========================================
897
+
898
+ input :title, hint: "Enter a descriptive title"
899
+
900
+ # ========================================
901
+ # Display Fields
902
+ # ========================================
903
+
904
+ display :content, as: :markdown
905
+
906
+ # ========================================
907
+ # Table Columns
908
+ # ========================================
909
+
910
+ column :view_count, align: :end
911
+
912
+ # ========================================
913
+ # Search, Filters & Scopes
914
+ # ========================================
915
+
916
+ search { |scope, query| scope.where("title ILIKE ?", "%#{query}%") }
917
+ filter :status, as: :select, choices: %w[draft published]
918
+
919
+ # ========================================
920
+ # Actions
921
+ # ========================================
922
+
923
+ action :publish, interaction: Posts::Publish
924
+ end
925
+ ```
926
+
927
+ ### 3. Order Within Each Section
928
+
929
+ Within each section, order declarations logically:
930
+
931
+ **For fields/inputs/displays:**
932
+ 1. Basic field type overrides first
933
+ 2. Fields with simple options next
934
+ 3. Fields with complex options or blocks last
935
+
936
+ **For filters and scopes:**
937
+ 1. Most commonly used first
938
+ 2. Grouped by related functionality
939
+
940
+ **For actions:**
941
+ 1. Primary actions first (create, publish)
942
+ 2. Secondary actions next (archive, duplicate)
943
+ 3. Destructive actions last (delete)
944
+
945
+ ### 4. Use `field` vs `input` vs `display` Appropriately
946
+
947
+ Understanding when to use each declaration:
948
+
949
+ ```ruby
950
+ class PostDefinition < Plutonium::Resource::Definition
951
+ # Use `field` when the configuration applies to ALL contexts
952
+ # (form inputs, show page displays, and table columns)
953
+ field :content, as: :rich_text # Changes type everywhere
954
+
955
+ # Use `input` for form-specific options
956
+ input :title, hint: "Shown only in forms", placeholder: "Form placeholder"
957
+
958
+ # Use `display` for show-page-specific options
959
+ display :content,
960
+ description: "Shown only on show pages",
961
+ wrapper: {class: "col-span-full"}
962
+
963
+ # Use `column` for table-specific options
964
+ column :title, label: "Custom header", align: :center
965
+ end
966
+ ```
967
+
968
+ ### 5. Avoid Redundant Declarations
969
+
970
+ Remember that **all model attributes are auto-detected**. Only declare overrides:
971
+
972
+ ::: code-group
973
+ ```ruby [āœ… Good - Only Overrides]
974
+ class PostDefinition < Plutonium::Resource::Definition
975
+ # Only override what needs customization
976
+ field :content, as: :rich_text
977
+ input :title, hint: "Enter a descriptive title"
978
+ display :metadata, wrapper: {class: "col-span-full"}
979
+ end
980
+ ```
981
+
982
+ ```ruby [āŒ Bad - Redundant]
983
+ class PostDefinition < Plutonium::Resource::Definition
984
+ # Unnecessary - these are auto-detected correctly
985
+ field :title, as: :string
986
+ field :content, as: :text
987
+ field :published_at, as: :datetime
988
+ field :author, as: :association
989
+
990
+ # Only these override defaults
991
+ field :content, as: :rich_text # This actually works, overrides the earlier :text
992
+ input :title, hint: "Enter a descriptive title"
993
+ end
994
+ ```
995
+ :::
996
+
997
+ ### 6. Complex Customizations at the End
998
+
999
+ Place complex blocks and custom components at the end of their sections:
1000
+
1001
+ ```ruby
1002
+ class PostDefinition < Plutonium::Resource::Definition
1003
+ # Simple overrides first
1004
+ input :title, hint: "Enter title"
1005
+ input :category, as: :select, choices: %w[Tech Business]
1006
+
1007
+ # Complex blocks last
1008
+ input :related_posts do |f|
1009
+ choices = if object.persisted?
1010
+ Post.where.not(id: object.id).published.pluck(:title, :id)
1011
+ else
1012
+ []
1013
+ end
1014
+ f.select_tag choices: choices
1015
+ end
1016
+
1017
+ display :status do |f|
1018
+ StatusBadgeComponent.new(
1019
+ status: f.value,
1020
+ class: "inline-flex items-center"
1021
+ )
1022
+ end
1023
+ end
1024
+ ```
1025
+
1026
+ ### 7. Example: Well-Structured Definition
1027
+
1028
+ Here's a complete example following all best practices:
1029
+
1030
+ ```ruby
1031
+ class PostDefinition < Plutonium::Resource::Definition
1032
+ # ========================================
1033
+ # Field Type Overrides
1034
+ # ========================================
1035
+
1036
+ field :content, as: :rich_text
1037
+ field :author_id, as: :hidden
1038
+
1039
+ # ========================================
1040
+ # Form Inputs
1041
+ # ========================================
1042
+
1043
+ # Basic fields with simple options
1044
+ input :title,
1045
+ label: "Post Title",
1046
+ hint: "Enter a descriptive title",
1047
+ placeholder: "e.g. Getting Started"
1048
+
1049
+ input :slug,
1050
+ hint: "Leave blank to auto-generate",
1051
+ class: "font-mono"
1052
+
1053
+ # Select inputs
1054
+ input :category, as: :select, choices: %w[Tech Business Lifestyle]
1055
+ input :author, as: :association
1056
+
1057
+ # Conditional inputs
1058
+ input :published_at,
1059
+ condition: -> { object.published? },
1060
+ hint: "When this post was published"
1061
+
1062
+ # ========================================
1063
+ # Display Fields
1064
+ # ========================================
1065
+
1066
+ display :content, as: :markdown
1067
+ display :view_count, label: "Total Views", class: "font-bold text-lg"
1068
+ display :metadata, wrapper: {class: "col-span-full"}
1069
+
1070
+ # ========================================
1071
+ # Table Columns
1072
+ # ========================================
1073
+
1074
+ column :title, label: "Article Title"
1075
+ column :category, align: :center
1076
+ column :view_count, align: :end, label: "Views"
1077
+ column :published_at, align: :end
1078
+
1079
+ # ========================================
1080
+ # Search & Filtering
1081
+ # ========================================
1082
+
1083
+ search do |scope, query|
1084
+ scope.where("title ILIKE ? OR content ILIKE ?", "%#{query}%", "%#{query}%")
1085
+ end
1086
+
1087
+ filter :status, as: :select, choices: %w[draft published archived]
1088
+ filter :category, as: :select, choices: %w[Tech Business Lifestyle]
1089
+ filter :published_after, as: :date
1090
+
1091
+ # ========================================
1092
+ # Scopes
1093
+ # ========================================
1094
+
1095
+ scope :published
1096
+ scope :draft
1097
+ scope :scheduled
1098
+
1099
+ # ========================================
1100
+ # Sorting
1101
+ # ========================================
1102
+
1103
+ sort :title
1104
+ sort :published_at
1105
+ sort :view_count
1106
+
1107
+ # ========================================
1108
+ # Actions
1109
+ # ========================================
1110
+
1111
+ action :publish, interaction: Posts::Publish
1112
+ action :schedule, interaction: Posts::Schedule
1113
+ action :archive, interaction: Posts::Archive
1114
+ end
1115
+ ```
1116
+
797
1117
  ## Related Modules
798
1118
 
799
1119
  - **[Resource Record](./resource_record.md)** - Resource controllers and CRUD operations
@@ -836,23 +1156,70 @@ field :name, as: :string, class: "custom-class", wrapper: {class: "field-wrapper
836
1156
  ```ruby
837
1157
  input :title,
838
1158
  as: :string,
839
- placeholder: "Enter title",
1159
+ # Field-level options (passed to the field wrapper):
1160
+ label: "Article Title", # Custom field label
1161
+ hint: "Enter a descriptive title", # Help text shown below input
1162
+ placeholder: "e.g. My First Post", # Input placeholder text
1163
+ # Tag-level options (passed to the input tag):
840
1164
  required: true,
841
1165
  class: "custom-input",
842
- wrapper: {class: "input-wrapper"},
843
1166
  data: {controller: "custom"},
844
- condition: -> { current_user.admin? }
1167
+ # Wrapper options:
1168
+ wrapper: {class: "input-wrapper"},
1169
+ # Conditional rendering:
1170
+ condition: -> { current_user.admin? },
1171
+ # Pre-submit for dynamic forms:
1172
+ pre_submit: false
845
1173
  ```
846
1174
 
1175
+ ::: tip Field-level vs Tag-level Options
1176
+ - **Field-level options** (`label`, `hint`, `placeholder`) are processed by the field wrapper and affect the field's label and help text display
1177
+ - **Tag-level options** (like `class`, `data`, `required`) are passed directly to the HTML input element
1178
+ - The system automatically routes options to the correct destination
1179
+ :::
1180
+
847
1181
  ### Display Options
848
1182
  ```ruby
849
1183
  display :content,
850
1184
  as: :markdown,
1185
+ # Field-level options (passed to the field wrapper):
1186
+ label: "Article Content", # Custom field label
1187
+ description: "Published content", # Description text shown below value
1188
+ placeholder: "No content yet", # Shown when value is empty
1189
+ # Tag-level options (passed to the display tag):
851
1190
  class: "prose",
1191
+ # Wrapper options:
852
1192
  wrapper: {class: "content-wrapper"},
1193
+ # Conditional rendering:
853
1194
  condition: -> { current_user.can_see_content? }
854
1195
  ```
855
1196
 
1197
+ ::: tip Display Uses Description, Forms Use Hint
1198
+ - **Display fields** use `:description` for explanatory text below the value
1199
+ - **Form fields** use `:hint` for help text below the input
1200
+ - Both support `:label` and `:placeholder` options
1201
+ :::
1202
+
1203
+ ### Column Options
1204
+ ```ruby
1205
+ column :title,
1206
+ as: :string,
1207
+ # Column-level options:
1208
+ label: "Article Title", # Custom column header
1209
+ align: :start, # Alignment: :start, :center, :end
1210
+ # Tag-level options (passed to the cell renderer):
1211
+ class: "font-bold",
1212
+ # Conditional rendering:
1213
+ condition: -> { current_user.admin? }
1214
+ ```
1215
+
1216
+ ::: tip Table Columns
1217
+ Table columns support a minimal set of field-level options since they render in a compact table format:
1218
+ - `:label` - Column header text
1219
+ - `:align` - Column alignment
1220
+ - Options like `:description` and `:placeholder` are filtered out as they don't make sense in table cells
1221
+ :::
1222
+
856
1223
  ### Choices Options (for selects)
857
1224
  ```ruby
858
1225
  # Static choices
@@ -89,7 +89,14 @@ module Plutonium
89
89
  return if conditionally_hidden
90
90
 
91
91
  tag = display_options[:as] || field_options[:as]
92
- tag_attributes = display_options.except(:wrapper, :as, :condition)
92
+
93
+ # Extract field-level options from display_options and merge into field_options
94
+ # These are Phlexi field options that should be passed to field(), not to the tag builder
95
+ field_level_keys = [:label, :description, :placeholder]
96
+ field_level_options = display_options.slice(*field_level_keys)
97
+ field_options = field_options.merge(field_level_options)
98
+
99
+ tag_attributes = display_options.except(:wrapper, :as, :condition, *field_level_keys)
93
100
  tag_block = display_definition[:block] || ->(f) {
94
101
  tag ||= f.inferred_field_component
95
102
  if tag.is_a?(Class)
@@ -88,7 +88,15 @@ module Plutonium
88
88
  input_options = input_definition[:options] || {}
89
89
 
90
90
  tag = input_options[:as] || field_options[:as]
91
- tag_attributes = input_options.except(:wrapper, :as, :pre_submit, :condition)
91
+
92
+ # Extract field-level options from input_options and merge into field_options
93
+ # These are Phlexi field options that should be passed to form.field(), not to the tag builder
94
+ # Note: forms use :hint, displays use :description
95
+ field_level_keys = [:hint, :label, :placeholder]
96
+ field_level_options = input_options.slice(*field_level_keys)
97
+ field_options = field_options.merge(field_level_options)
98
+
99
+ tag_attributes = input_options.except(:wrapper, :as, :pre_submit, :condition, *field_level_keys)
92
100
  if input_options[:pre_submit]
93
101
  tag_attributes["data-action"] = "change->form#preSubmit"
94
102
  end
@@ -65,8 +65,12 @@ module Plutonium
65
65
  next if conditionally_hidden
66
66
 
67
67
  tag = column_options[:as] || display_definition[:as] || field_options[:as]
68
- display_tag_attributes = display_options.except(:wrapper, :as, :condition)
69
- column_tag_attributes = column_options.except(:wrapper, :as, :align, :condition)
68
+
69
+ # Extract field-level options from display_options and column_options
70
+ # These are Phlexi field options that should NOT be passed to the tag builder
71
+ field_level_keys = [:label, :description, :placeholder]
72
+ display_tag_attributes = display_options.except(:wrapper, :as, :condition, *field_level_keys)
73
+ column_tag_attributes = column_options.except(:wrapper, :as, :align, :condition, *field_level_keys)
70
74
  tag_attributes = display_tag_attributes.merge(column_tag_attributes)
71
75
  tag_block = column_definition[:block] || ->(wrapped_object, key) {
72
76
  f = wrapped_object.field(key)
@@ -74,7 +78,9 @@ module Plutonium
74
78
  f.send(:"#{tag}_tag", **tag_attributes)
75
79
  }
76
80
 
77
- field_options = field_options.except(:condition).merge(**column_options.slice(:align))
81
+ # For table columns, only extract column-level options (label and align)
82
+ # Field-level options like description and placeholder don't make sense in table cells
83
+ field_options = field_options.except(:condition).merge(**column_options.slice(:align, :label))
78
84
  table.column name,
79
85
  **field_options,
80
86
  sort_params: current_query_object.sort_params_for(name),
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.26.11"
2
+ VERSION = "0.27.0"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :release do
4
+ desc "Display next version based on conventional commits"
5
+ task :next_version do
6
+ current_version = Plutonium::VERSION
7
+ puts "Current version: #{current_version}"
8
+
9
+ # Check for breaking changes, features, or fixes since last tag
10
+ breaking = `git log v#{current_version}..HEAD --oneline | grep -i "BREAKING CHANGE"`.strip
11
+ features = `git log v#{current_version}..HEAD --oneline | grep "^[a-f0-9]* feat"`.strip
12
+ fixes = `git log v#{current_version}..HEAD --oneline | grep "^[a-f0-9]* fix"`.strip
13
+
14
+ major, minor, patch = current_version.split(".").map(&:to_i)
15
+
16
+ if !breaking.empty?
17
+ next_version = "#{major + 1}.0.0"
18
+ puts "Next version (breaking changes): #{next_version}"
19
+ elsif !features.empty?
20
+ next_version = "#{major}.#{minor + 1}.0"
21
+ puts "Next version (new features): #{next_version}"
22
+ elsif !fixes.empty?
23
+ next_version = "#{major}.#{minor}.#{patch + 1}"
24
+ puts "Next version (bug fixes): #{next_version}"
25
+ else
26
+ puts "No changes detected"
27
+ end
28
+ end
29
+
30
+ desc "Prepare a new release"
31
+ task :prepare, [:version] do |_t, args|
32
+ version = args[:version]
33
+
34
+ unless version
35
+ puts "Usage: rake release:prepare[VERSION]"
36
+ puts "Example: rake release:prepare[0.27.0]"
37
+ exit 1
38
+ end
39
+
40
+ # Validate version format
41
+ unless version.match?(/^\d+\.\d+\.\d+$/)
42
+ puts "Error: Version must be in format X.Y.Z"
43
+ exit 1
44
+ end
45
+
46
+ # Update version.rb
47
+ version_file = "lib/plutonium/version.rb"
48
+ content = File.read(version_file)
49
+ updated_content = content.gsub(/VERSION = "[\d.]+"/, %{VERSION = "#{version}"})
50
+ File.write(version_file, updated_content)
51
+ puts "āœ“ Updated #{version_file}"
52
+
53
+ # Generate changelog using git-cliff
54
+ if system("which git-cliff > /dev/null 2>&1")
55
+ system("git-cliff --tag v#{version} -o CHANGELOG.md")
56
+ puts "āœ“ Generated CHANGELOG.md"
57
+ else
58
+ puts "⚠ git-cliff not found. Install with: brew install git-cliff"
59
+ puts " Skipping changelog generation"
60
+ end
61
+
62
+ puts "\nNext steps:"
63
+ puts "1. Review the changes:"
64
+ puts " git diff"
65
+ puts "2. Commit the version bump:"
66
+ puts " git add -A"
67
+ puts " git commit -m 'chore(release): prepare for v#{version}'"
68
+ puts "3. Create and push the tag:"
69
+ puts " git tag v#{version}"
70
+ puts " git push origin main --tags"
71
+ puts "4. Build and release the gem:"
72
+ puts " rake release:publish"
73
+ end
74
+
75
+ desc "Publish the gem to RubyGems"
76
+ task :publish do
77
+ # Reload version constant in case it was updated
78
+ load "lib/plutonium/version.rb"
79
+ version = Plutonium::VERSION
80
+
81
+ # Build the gem
82
+ puts "Building gem..."
83
+ system("gem build plutonium.gemspec") || abort("Gem build failed")
84
+
85
+ # Push to RubyGems
86
+ puts "Publishing to RubyGems..."
87
+ gem_file = "plutonium-#{version}.gem"
88
+ system("gem push #{gem_file}") || abort("Gem push failed")
89
+
90
+ puts "āœ“ Published plutonium #{version} to RubyGems"
91
+
92
+ # Clean up
93
+ File.delete(gem_file) if File.exist?(gem_file)
94
+ end
95
+
96
+ desc "Full release workflow"
97
+ task :full, [:version] do |_t, args|
98
+ version = args[:version]
99
+
100
+ unless version
101
+ puts "Usage: rake release:full[VERSION]"
102
+ exit 1
103
+ end
104
+
105
+ puts "Starting release workflow for v#{version}..."
106
+
107
+ # Check for uncommitted changes
108
+ unless `git status --porcelain`.strip.empty?
109
+ puts "Error: You have uncommitted changes. Please commit or stash them first."
110
+ exit 1
111
+ end
112
+
113
+ # Check we're on main branch
114
+ current_branch = `git branch --show-current`.strip
115
+ unless current_branch == "main" || current_branch == "master"
116
+ puts "Warning: You're not on main/master branch (current: #{current_branch})"
117
+ print "Continue anyway? [y/N] "
118
+ exit 1 unless $stdin.gets.strip.downcase == "y"
119
+ end
120
+
121
+ # Prepare release
122
+ Rake::Task["release:prepare"].invoke(version)
123
+
124
+ # Confirm before proceeding
125
+ puts "\nReady to commit, tag, and publish?"
126
+ print "Continue? [y/N] "
127
+ exit 0 unless $stdin.gets.strip.downcase == "y"
128
+
129
+ # Commit
130
+ system("git add -A")
131
+ system("git commit -m 'chore(release): prepare for v#{version}'")
132
+
133
+ # Push commit (without tags yet)
134
+ system("git push origin #{current_branch}")
135
+
136
+ # Publish gem (do this BEFORE tagging)
137
+ puts "\nPublishing gem to RubyGems..."
138
+ Rake::Task["release:publish"].invoke
139
+
140
+ # Only tag and push tag if publish succeeded
141
+ puts "\nCreating and pushing tag..."
142
+ system("git tag v#{version}")
143
+ system("git push origin v#{version}")
144
+
145
+ puts "\nāœ“ Release complete!"
146
+ puts "GitHub Actions will create the release shortly."
147
+ end
148
+ end
149
+
150
+ desc "Release tasks"
151
+ task release: ["release:next_version"]
data/plutonium.gemspec ADDED
@@ -0,0 +1,65 @@
1
+ require_relative "lib/plutonium/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "plutonium"
5
+ spec.version = Plutonium::VERSION
6
+ spec.authors = ["Stefan Froelich"]
7
+ spec.email = ["sfroelich01@gmail.com"]
8
+
9
+ spec.summary = "The Ultimate Rapid Application Development Toolkit (RADKit) for Rails"
10
+ spec.description = "Plutonium extends Rails' capabilities with a powerful, generator-driven toolkit designed to supercharge your development process. " \
11
+ "It transforms the way you build applications with Rails, optimizing for rapid application development."
12
+ spec.homepage = "https://radioactive-labs.github.io/plutonium-core/"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 3.2.2"
15
+
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://github.com/radioactive-labs/plutonium-core"
20
+ # spec.metadata["changelog_uri"] = "https://google.com"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(__dir__) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ (File.expand_path(f) == __FILE__) ||
27
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
28
+ end
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ spec.add_dependency "zeitwerk"
35
+ spec.add_dependency "rails", ">= 7.2"
36
+ spec.add_dependency "listen", "~> 3.8"
37
+ spec.add_dependency "pagy", "~> 9.0"
38
+ spec.add_dependency "rabl", "~> 0.16.1" # TODO: what to do with RABL
39
+ spec.add_dependency "semantic_range", "~> 3.0"
40
+ spec.add_dependency "tty-prompt", "~> 0.23.1"
41
+ spec.add_dependency "action_policy", "~> 0.7.0"
42
+ spec.add_dependency "phlex", "~> 2.0"
43
+ spec.add_dependency "phlex-rails"
44
+ spec.add_dependency "phlex-tabler_icons"
45
+ spec.add_dependency "phlexi-field", ">= 0.2.0"
46
+ spec.add_dependency "phlexi-form", ">= 0.10.0"
47
+ spec.add_dependency "phlexi-table", ">= 0.2.0"
48
+ spec.add_dependency "phlexi-display", ">= 0.2.0"
49
+ spec.add_dependency "phlexi-menu", ">= 0.4.0"
50
+ spec.add_dependency "tailwind_merge"
51
+ spec.add_dependency "phlex-slotable", ">= 1.0.0"
52
+ spec.add_dependency "redcarpet"
53
+
54
+ spec.add_development_dependency "rake"
55
+ spec.add_development_dependency "minitest"
56
+ spec.add_development_dependency "minitest-reporters"
57
+ spec.add_development_dependency "standard"
58
+ spec.add_development_dependency "brakeman"
59
+ spec.add_development_dependency "bundle-audit"
60
+ spec.add_development_dependency "appraisal"
61
+ spec.add_development_dependency "combustion"
62
+
63
+ # For more information and examples about making a new gem, check out our
64
+ # guide at: https://bundler.io/guides/creating_gem.html
65
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plutonium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.26.11
4
+ version: 0.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-28 00:00:00.000000000 Z
11
+ date: 2025-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -399,6 +399,7 @@ extensions: []
399
399
  extra_rdoc_files: []
400
400
  files:
401
401
  - "# Plutonium: The pre-alpha demo.md"
402
+ - ".cliff.toml"
402
403
  - ".node-version"
403
404
  - ".rspec"
404
405
  - ".ruby-version"
@@ -408,6 +409,7 @@ files:
408
409
  - ".vscode/settings.json"
409
410
  - Appraisals
410
411
  - CHANGELOG.md
412
+ - CONTRIBUTING.md
411
413
  - LICENSE.txt
412
414
  - README.md
413
415
  - Rakefile
@@ -887,9 +889,11 @@ files:
887
889
  - lib/rodauth/features/case_insensitive_login.rb
888
890
  - lib/rodauth/plugins.rb
889
891
  - lib/tasks/.keep
892
+ - lib/tasks/release.rake
890
893
  - package-lock.json
891
894
  - package.json
892
895
  - plutonium-blog-post.md
896
+ - plutonium.gemspec
893
897
  - postcss-gem-import.js
894
898
  - postcss.config.js
895
899
  - public/.keep