plutonium 0.42.0 → 0.43.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-controller/SKILL.md +38 -1
  3. data/.claude/skills/plutonium-definition/SKILL.md +14 -0
  4. data/.claude/skills/plutonium-forms/SKILL.md +16 -1
  5. data/.claude/skills/plutonium-profile/SKILL.md +276 -0
  6. data/.claude/skills/plutonium-views/SKILL.md +23 -1
  7. data/CHANGELOG.md +36 -0
  8. data/app/assets/plutonium.css +1 -1
  9. data/app/views/plutonium/_resource_header.html.erb +6 -27
  10. data/app/views/plutonium/_resource_sidebar.html.erb +1 -2
  11. data/app/views/resource/_resource_details.rabl +3 -2
  12. data/app/views/resource/index.rabl +3 -2
  13. data/app/views/resource/show.rabl +3 -2
  14. data/docs/guides/user-profile.md +322 -0
  15. data/docs/reference/controller/index.md +38 -1
  16. data/docs/reference/definition/index.md +16 -0
  17. data/docs/reference/views/forms.md +15 -0
  18. data/docs/reference/views/index.md +23 -1
  19. data/gemfiles/rails_7.gemfile.lock +1 -1
  20. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  21. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  22. data/lib/generators/pu/core/assets/assets_generator.rb +12 -0
  23. data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +11 -0
  24. data/lib/generators/pu/core/typespec/templates/common.tsp.tt +95 -0
  25. data/lib/generators/pu/core/typespec/templates/main.tsp.tt +27 -0
  26. data/lib/generators/pu/core/typespec/templates/main_multi.tsp.tt +25 -0
  27. data/lib/generators/pu/core/typespec/templates/model.tsp.tt +226 -0
  28. data/lib/generators/pu/core/typespec/typespec_generator.rb +342 -0
  29. data/lib/generators/pu/invites/USAGE +0 -1
  30. data/lib/generators/pu/invites/install_generator.rb +62 -15
  31. data/lib/generators/pu/invites/templates/db/migrate/create_user_invites.rb.tt +2 -2
  32. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +2 -0
  33. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/welcome_controller.rb.tt +1 -0
  34. data/lib/generators/pu/invites/templates/packages/invites/app/models/invites/user_invite.rb.tt +5 -5
  35. data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/signup.html.erb.tt +4 -4
  36. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +1 -1
  37. data/lib/generators/pu/lib/plutonium_generators/generator.rb +29 -0
  38. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +6 -23
  39. data/lib/generators/pu/pkg/portal/portal_generator.rb +5 -1
  40. data/lib/generators/pu/profile/USAGE +59 -0
  41. data/lib/generators/pu/profile/concerns/profile_arguments.rb +27 -0
  42. data/lib/generators/pu/profile/conn/USAGE +33 -0
  43. data/lib/generators/pu/profile/conn_generator.rb +167 -0
  44. data/lib/generators/pu/profile/install_generator.rb +119 -0
  45. data/lib/generators/pu/profile/setup/USAGE +42 -0
  46. data/lib/generators/pu/profile/setup_generator.rb +73 -0
  47. data/lib/generators/pu/rodauth/account_generator.rb +2 -4
  48. data/lib/generators/pu/rodauth/install_generator.rb +2 -2
  49. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +3 -0
  50. data/lib/generators/pu/saas/api_client_generator.rb +0 -2
  51. data/lib/generators/pu/saas/membership_generator.rb +68 -19
  52. data/lib/generators/pu/saas/setup_generator.rb +7 -2
  53. data/lib/generators/pu/saas/user_generator.rb +0 -2
  54. data/lib/plutonium/auth/rodauth.rb +8 -0
  55. data/lib/plutonium/core/controller.rb +7 -4
  56. data/lib/plutonium/core/controllers/authorizable.rb +5 -1
  57. data/lib/plutonium/definition/base.rb +7 -0
  58. data/lib/plutonium/helpers/display_helper.rb +6 -0
  59. data/lib/plutonium/profile/security_section.rb +118 -0
  60. data/lib/plutonium/resource/controller.rb +17 -7
  61. data/lib/plutonium/resource/controllers/interactive_actions.rb +11 -25
  62. data/lib/plutonium/resource/controllers/presentable.rb +46 -3
  63. data/lib/plutonium/resource/record/associated_with.rb +7 -1
  64. data/lib/plutonium/routing/mapper_extensions.rb +18 -18
  65. data/lib/plutonium/routing/route_set_extensions.rb +23 -2
  66. data/lib/plutonium/ui/breadcrumbs.rb +111 -131
  67. data/lib/plutonium/ui/dyna_frame/content.rb +12 -2
  68. data/lib/plutonium/ui/form/resource.rb +26 -19
  69. data/lib/plutonium/ui/page/base.rb +14 -14
  70. data/lib/plutonium/ui/table/components/selection_column.rb +6 -2
  71. data/lib/plutonium/ui/table/resource.rb +3 -2
  72. data/lib/plutonium/version.rb +1 -1
  73. data/package.json +1 -1
  74. metadata +17 -3
  75. data/lib/generators/pu/rodauth/concerns/gem_helpers.rb +0 -19
@@ -12,147 +12,127 @@ module Plutonium
12
12
  ol(
13
13
  class: "inline-flex items-center gap-1 md:gap-2"
14
14
  ) do
15
- # Dashboard
16
- li(class: "inline-flex items-center") do
17
- a(
18
- href: root_path,
19
- class: "inline-flex items-center text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 transition-colors"
20
- ) do
21
- svg(
22
- class: "w-3 h-3 me-2.5",
23
- aria_hidden: "true",
24
- xmlns: "http://www.w3.org/2000/svg",
25
- fill: "currentColor",
26
- viewbox: "0 0 20 20"
27
- ) do |s|
28
- s.path(
29
- d:
30
- "m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z"
31
- )
32
- end
33
- plain " Dashboard "
34
- end
35
- end
15
+ render_dashboard_link
16
+ render_parent_breadcrumbs if current_parent.present?
17
+ render_resource_breadcrumbs if resource_record?
18
+ render_trailing_separator
19
+ end
20
+ end
21
+ end
36
22
 
37
- # Parent
38
- if current_parent.present?
39
- # Parent Resource
40
- li(class: "flex items-center") do
41
- svg(
42
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
43
- aria_hidden: "true",
44
- xmlns: "http://www.w3.org/2000/svg",
45
- fill: "none",
46
- viewbox: "0 0 6 10"
47
- ) do |s|
48
- s.path(
49
- stroke: "currentColor",
50
- stroke_linecap: "round",
51
- stroke_linejoin: "round",
52
- stroke_width: "2",
53
- d: "m1 9 4-4-4-4"
54
- )
55
- end
56
- link_to resource_name_plural(current_parent.class),
57
- resource_url_for(current_parent.class, parent: nil),
58
- class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
59
- end
23
+ private
60
24
 
61
- # Parent Itself
62
- li(class: "flex items-center") do
63
- svg(
64
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
65
- aria_hidden: "true",
66
- xmlns: "http://www.w3.org/2000/svg",
67
- fill: "none",
68
- viewbox: "0 0 6 10"
69
- ) do |s|
70
- s.path(
71
- stroke: "currentColor",
72
- stroke_linecap: "round",
73
- stroke_linejoin: "round",
74
- stroke_width: "2",
75
- d: "m1 9 4-4-4-4"
76
- )
77
- end
78
- link_to display_name_of(current_parent),
79
- resource_url_for(current_parent, parent: nil),
80
- class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
81
- end
25
+ def render_dashboard_link
26
+ li(class: "inline-flex items-center") do
27
+ a(
28
+ href: root_path,
29
+ class: "inline-flex items-center text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 transition-colors"
30
+ ) do
31
+ svg(
32
+ class: "w-3 h-3 me-2.5",
33
+ aria_hidden: "true",
34
+ xmlns: "http://www.w3.org/2000/svg",
35
+ fill: "currentColor",
36
+ viewbox: "0 0 20 20"
37
+ ) do |s|
38
+ s.path(
39
+ d:
40
+ "m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z"
41
+ )
82
42
  end
43
+ plain " Dashboard "
44
+ end
45
+ end
46
+ end
83
47
 
84
- # Record
85
- if resource_record?
86
- unless current_engine.routes.resource_route_config_lookup[resource_class.model_name.plural][:route_type] == :resource
87
- # Record Resource
88
- li(class: "flex items-center") do
89
- svg(
90
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
91
- aria_hidden: "true",
92
- xmlns: "http://www.w3.org/2000/svg",
93
- fill: "none",
94
- viewbox: "0 0 6 10"
95
- ) do |s|
96
- s.path(
97
- stroke: "currentColor",
98
- stroke_linecap: "round",
99
- stroke_linejoin: "round",
100
- stroke_width: "2",
101
- d: "m1 9 4-4-4-4"
102
- )
103
- end
104
- link_to nestable_resource_name_plural(resource_class),
105
- resource_url_for(resource_class),
106
- class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
107
- end
108
- end
48
+ def render_parent_breadcrumbs
49
+ # Parent Resource
50
+ render_breadcrumb_item do
51
+ link_to resource_name_plural(current_parent.class),
52
+ resource_url_for(current_parent.class, parent: nil),
53
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
54
+ end
109
55
 
110
- # Record Itself
111
- if resource_record!.persisted? && action_name != "show"
112
- li(class: "flex items-center") do
113
- svg(
114
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
115
- aria_hidden: "true",
116
- xmlns: "http://www.w3.org/2000/svg",
117
- fill: "none",
118
- viewbox: "0 0 6 10"
119
- ) do |s|
120
- s.path(
121
- stroke: "currentColor",
122
- stroke_linecap: "round",
123
- stroke_linejoin: "round",
124
- stroke_width: "2",
125
- d: "m1 9 4-4-4-4"
126
- )
127
- end
128
- link_to display_name_of(resource_record!),
129
- resource_url_for(resource_record!),
130
- class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
131
- end
132
- end
133
- end
56
+ # Parent Itself
57
+ render_breadcrumb_item do
58
+ link_to display_name_of(current_parent),
59
+ resource_url_for(current_parent, parent: nil),
60
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
61
+ end
62
+ end
63
+
64
+ def render_resource_breadcrumbs
65
+ is_singular_route = current_engine.routes.resource_route_config_lookup[resource_class.model_name.plural][:route_type] == :resource
66
+
67
+ if is_singular_route
68
+ render_singular_resource_breadcrumb
69
+ else
70
+ render_plural_resource_breadcrumbs
71
+ end
72
+ end
134
73
 
135
- # Trailing Caret
136
- li(class: "flex items-center") do
137
- svg(
138
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
139
- aria_hidden: "true",
140
- xmlns: "http://www.w3.org/2000/svg",
141
- fill: "none",
142
- viewbox: "0 0 6 10"
143
- ) do |s|
144
- s.path(
145
- stroke: "currentColor",
146
- stroke_linecap: "round",
147
- stroke_linejoin: "round",
148
- stroke_width: "2",
149
- d: "m1 9 4-4-4-4"
150
- )
151
- end
74
+ def render_singular_resource_breadcrumb
75
+ render_breadcrumb_item do
76
+ if resource_record!.persisted? && action_name != "show"
77
+ link_to resource_name(resource_class),
78
+ resource_url_for(resource_record!),
79
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
80
+ else
81
+ span(class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] md:ms-2") do
82
+ plain resource_name(resource_class)
152
83
  end
153
84
  end
154
85
  end
155
86
  end
87
+
88
+ def render_plural_resource_breadcrumbs
89
+ # Resource index link
90
+ render_breadcrumb_item do
91
+ link_to nestable_resource_name_plural(resource_class),
92
+ resource_url_for(resource_class),
93
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
94
+ end
95
+
96
+ # Record itself (for non-singular routes only)
97
+ return unless resource_record!.persisted? && action_name != "show"
98
+
99
+ render_breadcrumb_item do
100
+ link_to display_name_of(resource_record!),
101
+ resource_url_for(resource_record!),
102
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
103
+ end
104
+ end
105
+
106
+ def render_trailing_separator
107
+ li(class: "flex items-center") do
108
+ render_chevron_separator
109
+ end
110
+ end
111
+
112
+ def render_breadcrumb_item(&)
113
+ li(class: "flex items-center") do
114
+ render_chevron_separator
115
+ yield
116
+ end
117
+ end
118
+
119
+ def render_chevron_separator
120
+ svg(
121
+ class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
122
+ aria_hidden: "true",
123
+ xmlns: "http://www.w3.org/2000/svg",
124
+ fill: "none",
125
+ viewbox: "0 0 6 10"
126
+ ) do |s|
127
+ s.path(
128
+ stroke: "currentColor",
129
+ stroke_linecap: "round",
130
+ stroke_linejoin: "round",
131
+ stroke_width: "2",
132
+ d: "m1 9 4-4-4-4"
133
+ )
134
+ end
135
+ end
156
136
  end
157
137
  end
158
138
  end
@@ -4,16 +4,26 @@ module Plutonium
4
4
  class Content < Plutonium::UI::Component::Base
5
5
  include Phlex::Rails::Helpers::TurboFrameTag
6
6
 
7
+ def initialize(content = nil)
8
+ @content = content
9
+ end
10
+
7
11
  def view_template
8
12
  if current_turbo_frame.present?
13
+ # Frame request: render only the turbo-frame with content
9
14
  turbo_frame_tag(current_turbo_frame) do
10
15
  render partial("flash")
11
- yield
16
+ @content&.call
12
17
  end
13
18
  else
14
- yield
19
+ # Regular request: yield self so caller can call frame.render_content
20
+ yield(self)
15
21
  end
16
22
  end
23
+
24
+ def render_content
25
+ @content&.call
26
+ end
17
27
  end
18
28
  end
19
29
  end
@@ -6,14 +6,15 @@ module Plutonium
6
6
  class Resource < Base
7
7
  include Plutonium::UI::Form::Concerns::RendersNestedResourceFields
8
8
 
9
- attr_reader :resource_fields, :resource_definition
9
+ attr_reader :resource_fields, :resource_definition, :singular_resource
10
10
 
11
11
  alias_method :record, :object
12
12
 
13
- def initialize(*, resource_fields:, resource_definition:, **, &)
13
+ def initialize(*, resource_fields:, resource_definition:, singular_resource: false, **, &)
14
14
  super(*, **, &)
15
15
  @resource_fields = resource_fields
16
16
  @resource_definition = resource_definition
17
+ @singular_resource = singular_resource
17
18
  end
18
19
 
19
20
  def form_template
@@ -35,28 +36,34 @@ module Plutonium
35
36
  input name: "return_to", value: request.params[:return_to], type: :hidden, hidden: true
36
37
 
37
38
  actions_wrapper {
38
- if object.respond_to?(:new_record?)
39
- if object.new_record?
40
- button(
41
- type: :submit,
42
- name: "return_to",
43
- value: request.url,
44
- class: "px-4 py-2 bg-secondary-600 text-white rounded-md hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-secondary-500"
45
- ) { "Create and add another" }
46
- else
47
- button(
48
- type: :submit,
49
- name: "return_to",
50
- value: request.url,
51
- class: "px-4 py-2 bg-secondary-600 text-white rounded-md hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-secondary-500"
52
- ) { "Update and continue editing" }
53
- end
54
- end
39
+ render_submit_and_continue_button if show_submit_and_continue?
55
40
 
56
41
  render submit_button
57
42
  }
58
43
  end
59
44
 
45
+ def show_submit_and_continue?
46
+ return false unless object.respond_to?(:new_record?)
47
+
48
+ # Check explicit configuration first
49
+ configured = resource_definition.submit_and_continue
50
+ return configured unless configured.nil?
51
+
52
+ # Auto-detect: hide for singular resources
53
+ !singular_resource
54
+ end
55
+
56
+ def render_submit_and_continue_button
57
+ label = object.new_record? ? "Create and add another" : "Update and continue editing"
58
+
59
+ button(
60
+ type: :submit,
61
+ name: "return_to",
62
+ value: request.url,
63
+ class: "px-4 py-2 bg-secondary-600 text-white rounded-md hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-secondary-500"
64
+ ) { label }
65
+ end
66
+
60
67
  def form_action
61
68
  return @form_action unless object.present? && @form_action != false && view_context.present?
62
69
 
@@ -10,18 +10,20 @@ module Plutonium
10
10
  @page_actions = page_actions
11
11
  end
12
12
 
13
- def view_template(&)
14
- render_before_header
15
- render_header
16
- render_after_header
13
+ def view_template(&block)
14
+ DynaFrameContent(page_content(block)) do |frame|
15
+ render_before_header
16
+ render_header
17
+ render_after_header
17
18
 
18
- render_before_content
19
- render_content(&)
20
- render_after_content
19
+ render_before_content
20
+ frame.render_content
21
+ render_after_content
21
22
 
22
- render_before_footer
23
- render_footer
24
- render_after_footer
23
+ render_before_footer
24
+ render_footer
25
+ render_after_footer
26
+ end
25
27
  end
26
28
 
27
29
  private
@@ -64,10 +66,8 @@ module Plutonium
64
66
  # Implement toolbar content
65
67
  end
66
68
 
67
- def render_content(&block)
68
- block ||= proc { render_default_content }
69
-
70
- DynaFrameContent(&block)
69
+ def page_content(block)
70
+ block || proc { render_default_content }
71
71
  end
72
72
 
73
73
  def render_default_content
@@ -12,7 +12,7 @@ module Plutonium
12
12
 
13
13
  def data_cell(wrapped_object)
14
14
  allowed_actions = compute_allowed_actions(wrapped_object.unwrapped)
15
- SelectionDataCell.new(wrapped_object.field(key).dom.value, allowed_actions)
15
+ SelectionDataCell.new(wrapped_object.field(value_key).dom.value, allowed_actions)
16
16
  end
17
17
 
18
18
  # Add hidden class and Stimulus target to header cell
@@ -34,6 +34,10 @@ module Plutonium
34
34
 
35
35
  private
36
36
 
37
+ def value_key
38
+ options[:value_key] || sample.class.primary_key.to_sym
39
+ end
40
+
37
41
  def bulk_actions
38
42
  options[:bulk_actions] || []
39
43
  end
@@ -43,7 +47,7 @@ module Plutonium
43
47
  end
44
48
 
45
49
  def compute_allowed_actions(record)
46
- return bulk_action_names unless policy_resolver
50
+ return bulk_actions.map { |a| a.name.to_s } unless policy_resolver
47
51
 
48
52
  policy = policy_resolver.call(record)
49
53
  bulk_actions.select { |action|
@@ -49,8 +49,9 @@ module Plutonium
49
49
 
50
50
  render Plutonium::UI::Table::Base.new(collection) do |table|
51
51
  # Selection column for bulk actions (hidden by default, Stimulus shows it)
52
- # Pass bulk actions and policy resolver for per-record authorization
53
- table.selection_column :id,
52
+ # Use :_selection as column key to avoid conflicts with field columns
53
+ # value_key defaults to model's primary_key
54
+ table.selection_column :_selection,
54
55
  bulk_actions:,
55
56
  policy_resolver: ->(record) { policy_for(record:) }
56
57
 
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.42.0"
2
+ VERSION = "0.43.0"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.42.0",
3
+ "version": "0.43.0",
4
4
  "description": "Build production-ready Rails apps in minutes, not days. Convention-driven, fully customizable, AI-ready.",
5
5
  "type": "module",
6
6
  "main": "src/js/core.js",
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.42.0
4
+ version: 0.43.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: 2026-02-14 00:00:00.000000000 Z
11
+ date: 2026-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -418,6 +418,7 @@ files:
418
418
  - ".claude/skills/plutonium-package/SKILL.md"
419
419
  - ".claude/skills/plutonium-policy/SKILL.md"
420
420
  - ".claude/skills/plutonium-portal/SKILL.md"
421
+ - ".claude/skills/plutonium-profile/SKILL.md"
421
422
  - ".claude/skills/plutonium-resource/SKILL.md"
422
423
  - ".claude/skills/plutonium-rodauth/SKILL.md"
423
424
  - ".claude/skills/plutonium-theming/SKILL.md"
@@ -543,6 +544,7 @@ files:
543
544
  - docs/guides/theming.md
544
545
  - docs/guides/troubleshooting.md
545
546
  - docs/guides/user-invites.md
547
+ - docs/guides/user-profile.md
546
548
  - docs/index.md
547
549
  - docs/og-image.html
548
550
  - docs/public/android-chrome-192x192.png
@@ -609,6 +611,11 @@ files:
609
611
  - lib/generators/pu/core/install/templates/config/packages.rb
610
612
  - lib/generators/pu/core/ruby/ruby_generator.rb
611
613
  - lib/generators/pu/core/ruby/templates/.keep
614
+ - lib/generators/pu/core/typespec/templates/common.tsp.tt
615
+ - lib/generators/pu/core/typespec/templates/main.tsp.tt
616
+ - lib/generators/pu/core/typespec/templates/main_multi.tsp.tt
617
+ - lib/generators/pu/core/typespec/templates/model.tsp.tt
618
+ - lib/generators/pu/core/typespec/typespec_generator.rb
612
619
  - lib/generators/pu/core/update/update_generator.rb
613
620
  - lib/generators/pu/docker/install/install_generator.rb
614
621
  - lib/generators/pu/docker/install/templates/.keep
@@ -719,6 +726,13 @@ files:
719
726
  - lib/generators/pu/pkg/portal/templates/app/views/package/dashboard/index.html.erb
720
727
  - lib/generators/pu/pkg/portal/templates/config/routes.rb.tt
721
728
  - lib/generators/pu/pkg/portal/templates/lib/engine.rb.tt
729
+ - lib/generators/pu/profile/USAGE
730
+ - lib/generators/pu/profile/concerns/profile_arguments.rb
731
+ - lib/generators/pu/profile/conn/USAGE
732
+ - lib/generators/pu/profile/conn_generator.rb
733
+ - lib/generators/pu/profile/install_generator.rb
734
+ - lib/generators/pu/profile/setup/USAGE
735
+ - lib/generators/pu/profile/setup_generator.rb
722
736
  - lib/generators/pu/res/conn/USAGE
723
737
  - lib/generators/pu/res/conn/conn_generator.rb
724
738
  - lib/generators/pu/res/conn/templates/.keep
@@ -745,7 +759,6 @@ files:
745
759
  - lib/generators/pu/rodauth/concerns/account_selector.rb
746
760
  - lib/generators/pu/rodauth/concerns/configuration.rb
747
761
  - lib/generators/pu/rodauth/concerns/feature_selector.rb
748
- - lib/generators/pu/rodauth/concerns/gem_helpers.rb
749
762
  - lib/generators/pu/rodauth/install_generator.rb
750
763
  - lib/generators/pu/rodauth/migration/active_record/account_expiration.erb
751
764
  - lib/generators/pu/rodauth/migration/active_record/active_sessions.erb
@@ -896,6 +909,7 @@ files:
896
909
  - lib/plutonium/portal/controller.rb
897
910
  - lib/plutonium/portal/dynamic_controllers.rb
898
911
  - lib/plutonium/portal/engine.rb
912
+ - lib/plutonium/profile/security_section.rb
899
913
  - lib/plutonium/query/adhoc_block.rb
900
914
  - lib/plutonium/query/base.rb
901
915
  - lib/plutonium/query/filter.rb
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Pu
4
- module Rodauth
5
- module Concerns
6
- module GemHelpers
7
- private
8
-
9
- def gem_in_bundle?(name)
10
- in_root do
11
- return true if File.exist?("Gemfile") && File.read("Gemfile").match?(/gem ['"]#{name}['"]/)
12
- return true if File.exist?("Gemfile.lock") && File.read("Gemfile.lock").include?(" #{name} ")
13
- end
14
- false
15
- end
16
- end
17
- end
18
- end
19
- end