From 3cd1b3b0fa440800ffc8bbc966477ed65aa7ceaf Mon Sep 17 00:00:00 2001 From: Mattia Roccoberton Date: Sun, 22 Mar 2026 14:10:25 +0000 Subject: [PATCH 1/4] feat(phlex): upgrade to Phlex 2 Update gemspec constraint from ~> 1, >= 1.10.0 to ~> 2. Replace deprecated unsafe_raw(content) with raw(safe(content)) in Content page. Replace removed yield_content(&block) with plain yield in DefaultLayout. All existing tests pass with Phlex 2.4.1. --- lib/tiny_admin/views/default_layout.rb | 4 ++-- lib/tiny_admin/views/pages/content.rb | 2 +- tiny_admin.gemspec | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/tiny_admin/views/default_layout.rb b/lib/tiny_admin/views/default_layout.rb index ca7270e..f14ba27 100644 --- a/lib/tiny_admin/views/default_layout.rb +++ b/lib/tiny_admin/views/default_layout.rb @@ -5,7 +5,7 @@ module Views class DefaultLayout < BasicLayout attr_accessor :flash_component, :head_component, :messages, :navbar_component, :options, :title - def view_template(&block) + def view_template extra_styles = TinyAdmin.settings.extra_styles flash_component&.messages = messages head_component&.update_attributes(page_title: title, style_links: style_links, extra_styles: extra_styles) @@ -20,7 +20,7 @@ def view_template(&block) main_content { render flash_component if flash_component - yield_content(&block) + yield } render_scripts diff --git a/lib/tiny_admin/views/pages/content.rb b/lib/tiny_admin/views/pages/content.rb index b860722..628fc5a 100644 --- a/lib/tiny_admin/views/pages/content.rb +++ b/lib/tiny_admin/views/pages/content.rb @@ -8,7 +8,7 @@ def view_template super do div(class: "content") { div(class: "content-data") { - unsafe_raw(content) + raw(safe(content)) } render TinyAdmin::Views::Components::Widgets.new(widgets) diff --git a/tiny_admin.gemspec b/tiny_admin.gemspec index 8bad8bd..0deda72 100644 --- a/tiny_admin.gemspec +++ b/tiny_admin.gemspec @@ -5,17 +5,17 @@ $:.push File.expand_path("lib", __dir__) require "tiny_admin/version" Gem::Specification.new do |spec| - spec.platform = Gem::Platform::RUBY - spec.name = "tiny_admin" - spec.version = TinyAdmin::VERSION - spec.summary = "Tiny Admin" + spec.platform = Gem::Platform::RUBY + spec.name = "tiny_admin" + spec.version = TinyAdmin::VERSION + spec.summary = "Tiny Admin" spec.description = "A compact and composable dashboard component for Ruby" - spec.license = "MIT" + spec.license = "MIT" spec.required_ruby_version = ">= 3.0.0" - spec.author = "Mattia Roccoberton" - spec.email = "mat@blocknot.es" + spec.author = "Mattia Roccoberton" + spec.email = "mat@blocknot.es" spec.homepage = "https://github.com/blocknotes/tiny_admin" spec.metadata = { @@ -25,10 +25,10 @@ Gem::Specification.new do |spec| "rubygems_mfa_required" => "true" } - spec.files = Dir["{app,db,lib}/**/*", "LICENSE.txt", "README.md"] + spec.files = Dir["{app,db,lib}/**/*", "LICENSE.txt", "README.md"] spec.require_paths = ["lib"] - spec.add_dependency "phlex", "~> 1", ">= 1.10.0" + spec.add_dependency "phlex", "~> 2" spec.add_dependency "roda", "~> 3" spec.add_dependency "tilt", "~> 2" spec.add_dependency "zeitwerk", "~> 2" From 57e05922956b29ce976074a43a05c3b3b9006c76 Mon Sep 17 00:00:00 2001 From: Mattia Roccoberton Date: Sun, 22 Mar 2026 14:10:45 +0000 Subject: [PATCH 2/4] test: add specs for configure, prepare_page, idempotent settings, and view components Add 31 new specs bringing line coverage to 100% and branch coverage from 80.97% to 83.4%. Covers TinyAdmin.configure block form, Utils#prepare_page, Settings#load_settings idempotency, and unit specs for FieldValue, Widgets, FiltersForm (all filter types), and ActionsButtons components. --- .../tiny_admin/settings_idempotent_spec.rb | 37 ++++++++++++ .../views/components/actions_buttons_spec.rb | 56 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 spec/lib/tiny_admin/settings_idempotent_spec.rb create mode 100644 spec/lib/tiny_admin/views/components/actions_buttons_spec.rb diff --git a/spec/lib/tiny_admin/settings_idempotent_spec.rb b/spec/lib/tiny_admin/settings_idempotent_spec.rb new file mode 100644 index 0000000..0dc9769 --- /dev/null +++ b/spec/lib/tiny_admin/settings_idempotent_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "dummy_rails_app" +require "rails_helper" + +RSpec.describe "Load Settings idempotency" do # rubocop:disable RSpec/DescribeClass + let(:settings) { TinyAdmin::Settings.instance } + + around do |example| + saved = settings.instance_variable_get(:@options)&.deep_dup + saved_store = settings.instance_variable_get(:@store) + saved_loaded = settings.instance_variable_get(:@loaded) + example.run + ensure + settings.instance_variable_set(:@options, saved) + settings.instance_variable_set(:@store, saved_store) + settings.instance_variable_set(:@loaded, saved_loaded) + end + + it "does not re-run on subsequent calls" do + settings.reset! + settings.load_settings + store = settings.store + settings.load_settings + expect(settings.store).to equal(store) + end + + it "runs again after reset!" do + settings.reset! + settings.load_settings + store = settings.store + + settings.reset! + settings.load_settings + expect(settings.store).not_to equal(store) + end +end diff --git a/spec/lib/tiny_admin/views/components/actions_buttons_spec.rb b/spec/lib/tiny_admin/views/components/actions_buttons_spec.rb new file mode 100644 index 0000000..282d6f2 --- /dev/null +++ b/spec/lib/tiny_admin/views/components/actions_buttons_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "dummy_rails_app" +require "rails_helper" + +RSpec.describe TinyAdmin::Views::Components::ActionsButtons do + let(:settings) { TinyAdmin::Settings.instance } + + before { settings.load_settings } + + describe "with no actions" do + it "renders an empty nav list", :aggregate_failures do + component = described_class.new + component.update_attributes(actions: {}, slug: "posts") + html = component.call + expect(html).to include("nav") + expect(html).not_to include("nav-item") + end + end + + describe "with actions" do + let(:action_class) do + Class.new do + def self.title + "Export" + end + end + end + + it "renders action buttons with links", :aggregate_failures do + component = described_class.new + component.update_attributes(actions: { "export" => action_class }, slug: "posts") + html = component.call + expect(html).to include("Export") + expect(html).to include("export") + expect(html).to include("nav-item") + end + + it "includes the reference in the URL when provided" do + component = described_class.new + component.update_attributes(actions: { "export" => action_class }, slug: "posts", reference: "42") + html = component.call + expect(html).to include("42") + end + end + + describe "with an action class that does not respond to title" do + it "falls back to the action key as label" do + action_class = Class.new + component = described_class.new + component.update_attributes(actions: { "download" => action_class }, slug: "posts") + html = component.call + expect(html).to include("download") + end + end +end From a7f69066920b41b1cc4cfaf38f75766ce381fcf7 Mon Sep 17 00:00:00 2001 From: Mattia Roccoberton Date: Sun, 22 Mar 2026 14:21:38 +0000 Subject: [PATCH 3/4] fix(rbs): update type signatures for Phlex 2, new classes, and refactored methods The RBS runtime type checker was failing because signatures were stale after the Phlex 2 upgrade and code refactoring. Key changes: - Fix view_template block arity: (untyped) -> () for Phlex 2 yield pattern - Add signatures for Attributes module, ActionsButtons, and ErrorPage - Update BasicLayout/BasicComponent to include Attributes module - Add authorize! to Router, @loaded to Settings - Remove stale actions_buttons from Index/Show (now a component) --- sig/tiny_admin/router.rbs | 2 ++ sig/tiny_admin/settings.rbs | 3 ++- sig/tiny_admin/views/actions/index.rbs | 4 +--- sig/tiny_admin/views/actions/show.rbs | 6 +----- sig/tiny_admin/views/attributes.rbs | 7 +++++++ sig/tiny_admin/views/basic_layout.rbs | 3 +-- sig/tiny_admin/views/components/actions_buttons.rbs | 13 +++++++++++++ sig/tiny_admin/views/components/basic_component.rbs | 2 +- sig/tiny_admin/views/default_layout.rbs | 2 +- sig/tiny_admin/views/pages/content.rbs | 2 +- sig/tiny_admin/views/pages/error_page.rbs | 13 +++++++++++++ sig/tiny_admin/views/pages/page_not_allowed.rbs | 4 +--- sig/tiny_admin/views/pages/page_not_found.rbs | 4 +--- sig/tiny_admin/views/pages/record_not_found.rbs | 4 +--- sig/tiny_admin/views/pages/root.rbs | 2 +- sig/tiny_admin/views/pages/simple_auth_login.rbs | 2 +- 16 files changed, 48 insertions(+), 25 deletions(-) create mode 100644 sig/tiny_admin/views/attributes.rbs create mode 100644 sig/tiny_admin/views/components/actions_buttons.rbs create mode 100644 sig/tiny_admin/views/pages/error_page.rbs diff --git a/sig/tiny_admin/router.rbs b/sig/tiny_admin/router.rbs index dc92055..7731097 100644 --- a/sig/tiny_admin/router.rbs +++ b/sig/tiny_admin/router.rbs @@ -6,6 +6,8 @@ module TinyAdmin def authorization: () -> untyped + def authorize!: (Symbol, ?String?) { () -> void } -> void + def render_page: (untyped) -> void def root_route: (untyped) -> void diff --git a/sig/tiny_admin/settings.rbs b/sig/tiny_admin/settings.rbs index b58026c..4614a03 100644 --- a/sig/tiny_admin/settings.rbs +++ b/sig/tiny_admin/settings.rbs @@ -4,8 +4,9 @@ module TinyAdmin OPTIONS: Array[Symbol] @options: Hash[Array[Symbol], untyped] + @loaded: bool - attr_reader store: Store + attr_reader store: Store? # Dynamically defined accessors for OPTIONS def authentication: () -> untyped diff --git a/sig/tiny_admin/views/actions/index.rbs b/sig/tiny_admin/views/actions/index.rbs index 4fc8b3d..ec3003b 100644 --- a/sig/tiny_admin/views/actions/index.rbs +++ b/sig/tiny_admin/views/actions/index.rbs @@ -11,12 +11,10 @@ module TinyAdmin attr_accessor records: Enumerable[untyped] attr_accessor slug: String - def view_template: () ?{ (untyped) -> void } -> void + def view_template: () ?{ () -> void } -> void private - def actions_buttons: () -> void - def table_body: () -> void def table_header: () -> void diff --git a/sig/tiny_admin/views/actions/show.rbs b/sig/tiny_admin/views/actions/show.rbs index e2f20bf..e776b1f 100644 --- a/sig/tiny_admin/views/actions/show.rbs +++ b/sig/tiny_admin/views/actions/show.rbs @@ -9,11 +9,7 @@ module TinyAdmin attr_accessor reference: untyped attr_accessor slug: String - def view_template: () ?{ (untyped) -> void } -> void - - private - - def actions_buttons: () -> void + def view_template: () ?{ () -> void } -> void end end end diff --git a/sig/tiny_admin/views/attributes.rbs b/sig/tiny_admin/views/attributes.rbs new file mode 100644 index 0000000..634d313 --- /dev/null +++ b/sig/tiny_admin/views/attributes.rbs @@ -0,0 +1,7 @@ +module TinyAdmin + module Views + module Attributes + def update_attributes: (Hash[Symbol, untyped]) -> void + end + end +end diff --git a/sig/tiny_admin/views/basic_layout.rbs b/sig/tiny_admin/views/basic_layout.rbs index 280404c..ddbb0de 100644 --- a/sig/tiny_admin/views/basic_layout.rbs +++ b/sig/tiny_admin/views/basic_layout.rbs @@ -1,6 +1,7 @@ module TinyAdmin module Views class BasicLayout + include Attributes include Utils attr_accessor content: untyped @@ -8,8 +9,6 @@ module TinyAdmin attr_accessor widgets: untyped def label_for: (String, options: Array[untyped]) -> String? - - def update_attributes: (Hash[Symbol, untyped]) -> void end end end diff --git a/sig/tiny_admin/views/components/actions_buttons.rbs b/sig/tiny_admin/views/components/actions_buttons.rbs new file mode 100644 index 0000000..df19778 --- /dev/null +++ b/sig/tiny_admin/views/components/actions_buttons.rbs @@ -0,0 +1,13 @@ +module TinyAdmin + module Views + module Components + class ActionsButtons < BasicComponent + attr_accessor actions: Hash[String, untyped]? + attr_accessor slug: String? + attr_accessor reference: untyped + + def view_template: () -> void + end + end + end +end diff --git a/sig/tiny_admin/views/components/basic_component.rbs b/sig/tiny_admin/views/components/basic_component.rbs index 389a8ff..61441b3 100644 --- a/sig/tiny_admin/views/components/basic_component.rbs +++ b/sig/tiny_admin/views/components/basic_component.rbs @@ -2,7 +2,7 @@ module TinyAdmin module Views module Components class BasicComponent - def update_attributes: (Hash[Symbol, untyped]) -> void + include Attributes end end end diff --git a/sig/tiny_admin/views/default_layout.rbs b/sig/tiny_admin/views/default_layout.rbs index d91d002..be9c821 100644 --- a/sig/tiny_admin/views/default_layout.rbs +++ b/sig/tiny_admin/views/default_layout.rbs @@ -8,7 +8,7 @@ module TinyAdmin attr_accessor options: Array[Symbol]? attr_accessor title: String? - def view_template: () ?{ (untyped) -> void } -> void + def view_template: () ?{ () -> void } -> void private diff --git a/sig/tiny_admin/views/pages/content.rbs b/sig/tiny_admin/views/pages/content.rbs index 2f49232..8f82ed6 100644 --- a/sig/tiny_admin/views/pages/content.rbs +++ b/sig/tiny_admin/views/pages/content.rbs @@ -2,7 +2,7 @@ module TinyAdmin module Views module Pages class Content < DefaultLayout - def view_template: () ?{ () -> void } -> void + def view_template: () -> void end end end diff --git a/sig/tiny_admin/views/pages/error_page.rbs b/sig/tiny_admin/views/pages/error_page.rbs new file mode 100644 index 0000000..a3dfafc --- /dev/null +++ b/sig/tiny_admin/views/pages/error_page.rbs @@ -0,0 +1,13 @@ +module TinyAdmin + module Views + module Pages + class ErrorPage < DefaultLayout + def view_template: () -> void + + private + + def css_class: () -> String + end + end + end +end diff --git a/sig/tiny_admin/views/pages/page_not_allowed.rbs b/sig/tiny_admin/views/pages/page_not_allowed.rbs index f42b57d..d6212b2 100644 --- a/sig/tiny_admin/views/pages/page_not_allowed.rbs +++ b/sig/tiny_admin/views/pages/page_not_allowed.rbs @@ -1,9 +1,7 @@ module TinyAdmin module Views module Pages - class PageNotAllowed < DefaultLayout - def view_template: () ?{ () -> void } -> void - + class PageNotAllowed < ErrorPage def title: () -> String end end diff --git a/sig/tiny_admin/views/pages/page_not_found.rbs b/sig/tiny_admin/views/pages/page_not_found.rbs index ff4d731..798ff9d 100644 --- a/sig/tiny_admin/views/pages/page_not_found.rbs +++ b/sig/tiny_admin/views/pages/page_not_found.rbs @@ -1,9 +1,7 @@ module TinyAdmin module Views module Pages - class PageNotFound < DefaultLayout - def view_template: () ?{ () -> void } -> void - + class PageNotFound < ErrorPage def title: () -> String end end diff --git a/sig/tiny_admin/views/pages/record_not_found.rbs b/sig/tiny_admin/views/pages/record_not_found.rbs index 8711712..e92ce59 100644 --- a/sig/tiny_admin/views/pages/record_not_found.rbs +++ b/sig/tiny_admin/views/pages/record_not_found.rbs @@ -1,9 +1,7 @@ module TinyAdmin module Views module Pages - class RecordNotFound < DefaultLayout - def view_template: () ?{ () -> void } -> void - + class RecordNotFound < ErrorPage def title: () -> String end end diff --git a/sig/tiny_admin/views/pages/root.rbs b/sig/tiny_admin/views/pages/root.rbs index 6998bff..87fa779 100644 --- a/sig/tiny_admin/views/pages/root.rbs +++ b/sig/tiny_admin/views/pages/root.rbs @@ -2,7 +2,7 @@ module TinyAdmin module Views module Pages class Root < DefaultLayout - def view_template: () ?{ () -> void } -> void + def view_template: () -> void end end end diff --git a/sig/tiny_admin/views/pages/simple_auth_login.rbs b/sig/tiny_admin/views/pages/simple_auth_login.rbs index 9592304..ebd7a3b 100644 --- a/sig/tiny_admin/views/pages/simple_auth_login.rbs +++ b/sig/tiny_admin/views/pages/simple_auth_login.rbs @@ -2,7 +2,7 @@ module TinyAdmin module Views module Pages class SimpleAuthLogin < DefaultLayout - def view_template: () ?{ () -> void } -> void + def view_template: () -> void def title: () -> String end From 28e024d9170fc92b91d905f7b454464912b67bb4 Mon Sep 17 00:00:00 2001 From: Mattia Roccoberton Date: Sun, 22 Mar 2026 15:32:27 +0100 Subject: [PATCH 4/4] feat!: Set minimum Ruby version to 3.2 --- .github/workflows/linters.yml | 2 +- .github/workflows/tests.yml | 2 +- .rubocop.yml | 2 +- lib/tiny_admin/context.rb | 3 +-- tiny_admin.gemspec | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index d45d01d..0a912a1 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: '3.2' bundler-cache: true - name: Set up Reviewdog diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 98fa8ad..5e99665 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - ruby: ['3.0', '3.1', '3.2', '3.3', '3.4', '4.0'] + ruby: ['3.2', '3.3', '3.4', '4.0'] steps: - name: Checkout repository diff --git a/.rubocop.yml b/.rubocop.yml index 758c46e..f16b487 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,7 +15,7 @@ AllCops: - vendor/**/* NewCops: enable SuggestExtensions: false - TargetRubyVersion: 3.0 + TargetRubyVersion: 3.2 Lint/MissingSuper: Exclude: diff --git a/lib/tiny_admin/context.rb b/lib/tiny_admin/context.rb index bec27dc..4227ab6 100644 --- a/lib/tiny_admin/context.rb +++ b/lib/tiny_admin/context.rb @@ -7,7 +7,6 @@ module TinyAdmin :repository, :request, :router, - :slug, - keyword_init: true + :slug ) end diff --git a/tiny_admin.gemspec b/tiny_admin.gemspec index 0deda72..90d7baa 100644 --- a/tiny_admin.gemspec +++ b/tiny_admin.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |spec| spec.description = "A compact and composable dashboard component for Ruby" spec.license = "MIT" - spec.required_ruby_version = ">= 3.0.0" + spec.required_ruby_version = ">= 3.2.0" spec.author = "Mattia Roccoberton" spec.email = "mat@blocknot.es"