diff --git a/app/assets/stylesheets/flash.css b/app/assets/stylesheets/flash.css index a717838e4..0d369dfbb 100644 --- a/app/assets/stylesheets/flash.css +++ b/app/assets/stylesheets/flash.css @@ -16,7 +16,9 @@ color: var(--flash-color, var(--color-ink-inverted)); display: inline-flex; font-size: var(--font-size-medium); + inline-size: max-content; margin: 0 auto; + max-inline-size: 90vw; padding: 0.7em 1.4em; } } diff --git a/app/models/webhook.rb b/app/models/webhook.rb index 3b34af041..f008aa934 100644 --- a/app/models/webhook.rb +++ b/app/models/webhook.rb @@ -36,6 +36,7 @@ class Webhook < ApplicationRecord after_create :create_delinquency_tracker! normalizes :subscribed_actions, with: ->(value) { Array.wrap(value).map(&:to_s).uniq & PERMITTED_ACTIONS } + normalizes :url, with: -> { it.strip } validates :name, presence: true validate :validate_url diff --git a/app/views/boards/edit.html.erb b/app/views/boards/edit.html.erb index cd28b29a4..5895ef65f 100644 --- a/app/views/boards/edit.html.erb +++ b/app/views/boards/edit.html.erb @@ -16,13 +16,13 @@
<%= form_with model: @board, class: "display-contents", data: { - controller: "form boards-form", + controller: "form boards-form bridge--form", boards_form_self_removal_prompt_message_value: "Are you sure you want to remove yourself from this board? You won’t be able to get back in unless someone invites you.", action: "turbo:submit-start->boards-form#submitWithWarning" } do |form| %> <%= render "boards/edit/name", form: form, board: @board %> <%= render "boards/edit/users", board: @board, selected_users: @selected_users, unselected_users: @unselected_users, form: form %> - diff --git a/docs/API.md b/docs/API.md index 47965372c..7ac8dae02 100644 --- a/docs/API.md +++ b/docs/API.md @@ -539,6 +539,7 @@ __Response:__ "description": "Hello, World!", "description_html": "

Hello, World!

", "image_url": null, + "has_attachments": false, "tags": ["programming"], "golden": false, "last_active_at": "2025-12-05T19:38:48.553Z", @@ -590,6 +591,7 @@ __Response:__ "description": "Hello, World!", "description_html": "

Hello, World!

", "image_url": null, + "has_attachments": false, "tags": ["programming"], "closed": false, "golden": false, @@ -857,6 +859,7 @@ __Response:__ "description": "Hello, World!", "description_html": "

Hello, World!

", "image_url": null, + "has_attachments": false, "tags": ["programming"], "golden": false, "last_active_at": "2025-12-05T19:38:48.553Z", diff --git a/docs/kamal-deployment.md b/docs/kamal-deployment.md index 19aa208c8..60e1b345a 100644 --- a/docs/kamal-deployment.md +++ b/docs/kamal-deployment.md @@ -11,7 +11,7 @@ The steps to configure your very own Fizzy are: 1. Fork the repo 2. Initialize Kamal by running `kamal init`. This command generates the `.kamal` directory along with the required configuration files, including `.kamal/secrets`. -3. Edit few things in config/deploy.yml and .kamal/secrets +3. Edit a few things in config/deploy.yml and .kamal/secrets 4. Run `kamal setup` to do your first deploy. We'll go through each of these in turn. diff --git a/lib/rails_ext/active_storage_analyze_job_skip_detached.rb b/lib/rails_ext/active_storage_analyze_job_skip_detached.rb new file mode 100644 index 000000000..0df49f0cf --- /dev/null +++ b/lib/rails_ext/active_storage_analyze_job_skip_detached.rb @@ -0,0 +1,14 @@ +# Skip analysis for blobs whose attachments have already been destroyed. +# e.g. when a user uploads a file but deletes it before the analysis runs. +# Avoids `Aws::S3::Errors::NoSuchKey` when an upload is deleted before AnalyzeJob runs. +module ActiveStorageAnalyzeJobSkipDetached + def perform(blob) + return unless blob.attachments.exists? + + super + end +end + +ActiveSupport.on_load :active_storage_blob do + ActiveStorage::AnalyzeJob.prepend ActiveStorageAnalyzeJobSkipDetached +end diff --git a/saas/README.md b/saas/README.md index b71afff08..bec3b279f 100644 --- a/saas/README.md +++ b/saas/README.md @@ -14,7 +14,7 @@ To go back to open source mode: bin/rails saas:disable ``` -Then you can work do [Fizzy development as usual](https://github.com/basecamp/fizzy). +Then you can do [Fizzy development as usual](https://github.com/basecamp/fizzy). ## How to update Fizzy diff --git a/saas/app/models/account/billing.rb b/saas/app/models/account/billing.rb index 02886581a..c124fd64c 100644 --- a/saas/app/models/account/billing.rb +++ b/saas/app/models/account/billing.rb @@ -27,7 +27,7 @@ module Account::Billing end def uncomp - billing_waiver&.destroy + billing_waiver&.destroy! reload_billing_waiver end diff --git a/saas/app/models/account/limited.rb b/saas/app/models/account/limited.rb index 8e846a117..2918e90a3 100644 --- a/saas/app/models/account/limited.rb +++ b/saas/app/models/account/limited.rb @@ -21,11 +21,11 @@ module Account::Limited end def nearing_plan_cards_limit? - plan.limit_cards? && remaining_cards_count < NEAR_CARD_LIMIT_THRESHOLD + plan.limit_cards? && remaining_cards_count <= NEAR_CARD_LIMIT_THRESHOLD end def exceeding_card_limit? - plan.limit_cards? && billed_cards_count > plan.card_limit + plan.limit_cards? && billed_cards_count >= plan.card_limit end def nearing_plan_storage_limit? diff --git a/saas/test/models/account/limited_test.rb b/saas/test/models/account/limited_test.rb index 828386023..fff154451 100644 --- a/saas/test/models/account/limited_test.rb +++ b/saas/test/models/account/limited_test.rb @@ -10,10 +10,11 @@ class Account::LimitedTest < ActiveSupport::TestCase accounts(:initech).update_column(:cards_count, 899) assert_not accounts(:initech).nearing_plan_cards_limit? - # Free plan near limit + # Free plan at exactly the threshold (remaining = 100) accounts(:initech).update_column(:cards_count, 900) - assert_not accounts(:initech).nearing_plan_cards_limit? + assert accounts(:initech).nearing_plan_cards_limit? + # Free plan over the threshold accounts(:initech).update_column(:cards_count, 901) assert accounts(:initech).nearing_plan_cards_limit? end @@ -27,6 +28,10 @@ class Account::LimitedTest < ActiveSupport::TestCase accounts(:initech).update_column(:cards_count, 999) assert_not accounts(:initech).exceeding_card_limit? + # Free plan at exactly the limit + accounts(:initech).update_column(:cards_count, 1000) + assert accounts(:initech).exceeding_card_limit? + # Free plan over limit accounts(:initech).update_column(:cards_count, 1001) assert accounts(:initech).exceeding_card_limit? diff --git a/test/lib/rails_ext/active_storage_analyze_job_skip_detached_test.rb b/test/lib/rails_ext/active_storage_analyze_job_skip_detached_test.rb new file mode 100644 index 000000000..5ab5edaff --- /dev/null +++ b/test/lib/rails_ext/active_storage_analyze_job_skip_detached_test.rb @@ -0,0 +1,23 @@ +require "test_helper" + +class ActiveStorageAnalyzeJobSkipDetachedTest < ActiveSupport::TestCase + test "skips analysis when blob has no attachments" do + blob = ActiveStorage::Blob.create_and_upload!( + io: StringIO.new("x" * 1024), filename: "orphan.txt", content_type: "text/plain" + ) + + blob.expects(:analyze).never + + ActiveStorage::AnalyzeJob.perform_now(blob) + end + + test "performs analysis when blob has attachments" do + card = cards(:logo) + card.image.attach io: StringIO.new("x" * 1024), filename: "test.png", content_type: "image/png" + blob = card.image.blob + + blob.expects(:analyze).once + + ActiveStorage::AnalyzeJob.perform_now(blob) + end +end diff --git a/test/models/webhook_test.rb b/test/models/webhook_test.rb index 52c43383d..121c1a651 100644 --- a/test/models/webhook_test.rb +++ b/test/models/webhook_test.rb @@ -35,6 +35,10 @@ class WebhookTest < ActiveSupport::TestCase webhook = Webhook.new name: "HTTPS", board: boards(:writebook), url: "https://example.com/webhook" assert webhook.valid? + + webhook = Webhook.new name: "TRAILING SPACE", board: boards(:writebook), url: "https://example.com/webhook " + assert webhook.valid? + assert_equal "https://example.com/webhook", webhook.url end test "deactivate" do