From d3258b3c7f9076ad8b1002983cfa89d95e385aea Mon Sep 17 00:00:00 2001 From: Jeroen Versteeg Date: Fri, 9 Jan 2026 16:31:47 +0100 Subject: [PATCH 01/22] Replace use of instance variable with local The partial `boards/edit/auto_close` used both `@board` as well as `board`. --- app/views/boards/edit/_auto_close.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/boards/edit/_auto_close.html.erb b/app/views/boards/edit/_auto_close.html.erb index c32c1c050..8993c132e 100644 --- a/app/views/boards/edit/_auto_close.html.erb +++ b/app/views/boards/edit/_auto_close.html.erb @@ -1,7 +1,7 @@ -<%= turbo_frame_tag @board, :entropy do %> +<%= turbo_frame_tag board, :entropy do %>

Auto close

Fizzy doesn’t let stale cards stick around forever. Cards automatically move to “Not now” if no one updates, comments, or moves a card for…

- <%= render "entropy/auto_close", model: board, url: board_entropy_path(board), disabled: !Current.user.can_administer_board?(@board) %> + <%= render "entropy/auto_close", model: board, url: board_entropy_path(board), disabled: !Current.user.can_administer_board?(board) %>
<% end %> From 6895135977e3d4fbe4493b1a5593aa4f115f4020 Mon Sep 17 00:00:00 2001 From: secretpray Date: Mon, 12 Jan 2026 18:44:35 +0200 Subject: [PATCH 02/22] Fix deprecation warnings for skip_before_action :verify_authenticity_token Replace deprecated `skip_before_action :verify_authenticity_token` with `skip_forgery_protection` to resolve Rails deprecation warnings. Changes: - app/controllers/notifications/unsubscribes_controller.rb - saas/app/controllers/stripe/webhooks_controller.rb --- app/controllers/notifications/unsubscribes_controller.rb | 2 +- saas/app/controllers/stripe/webhooks_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/notifications/unsubscribes_controller.rb b/app/controllers/notifications/unsubscribes_controller.rb index f7486d91e..d1842b169 100644 --- a/app/controllers/notifications/unsubscribes_controller.rb +++ b/app/controllers/notifications/unsubscribes_controller.rb @@ -1,6 +1,6 @@ class Notifications::UnsubscribesController < ApplicationController allow_unauthenticated_access - skip_before_action :verify_authenticity_token + skip_forgery_protection before_action :set_user diff --git a/saas/app/controllers/stripe/webhooks_controller.rb b/saas/app/controllers/stripe/webhooks_controller.rb index cdfb6bcec..13dcd4699 100644 --- a/saas/app/controllers/stripe/webhooks_controller.rb +++ b/saas/app/controllers/stripe/webhooks_controller.rb @@ -1,7 +1,7 @@ class Stripe::WebhooksController < ApplicationController allow_unauthenticated_access skip_before_action :require_account - skip_before_action :verify_authenticity_token + skip_forgery_protection def create if event = verify_webhook_signature From 006e61ea5898ad470a7ef295895ca8b83e60a1ef Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Mon, 12 Jan 2026 10:30:18 -0800 Subject: [PATCH 03/22] Allow ActionText embed reuse and safe purge (#2346) --- .../initializers/active_storage_no_reuse.rb | 35 +++++---- ...active_storage_purge_on_last_attachment.rb | 40 ++++++++++ script/migrations/backfill-storage-ledger.rb | 6 +- test/models/storage/no_reuse_test.rb | 77 +++++++++++++++++++ 4 files changed, 140 insertions(+), 18 deletions(-) create mode 100644 config/initializers/active_storage_purge_on_last_attachment.rb diff --git a/config/initializers/active_storage_no_reuse.rb b/config/initializers/active_storage_no_reuse.rb index 2def087fe..0bd057cde 100644 --- a/config/initializers/active_storage_no_reuse.rb +++ b/config/initializers/active_storage_no_reuse.rb @@ -16,6 +16,8 @@ # attached to an untracked type (avatar/export) could theoretically be reused in a tracked # context. This is acceptable - user-accessible blob IDs from untracked contexts are # basically nonexistent in practice. +# +# Exception: ActionText embeds are allowed to reuse blobs to support copy/paste. ActiveSupport.on_load(:active_storage_attachment) do validate :blob_account_matches_record, on: :create @@ -29,30 +31,31 @@ ActiveSupport.on_load(:active_storage_attachment) do # bypass tenancy checks via try(:account) returning nil - this is intentional as # these classes don't participate in storage tracking. def blob_account_matches_record - return unless record&.try(:account).present? - return if whitelisted_for_cross_account? - - unless blob&.account_id == record.account.id - errors.add(:blob_id, "blob account must match record account") + if record&.try(:account).present? && !whitelisted_for_cross_account? + unless blob&.account_id == record.account.id + errors.add(:blob_id, "blob account must match record account") + end end end # Ledger integrity: blob can only have one tracked attachment def no_tracked_blob_reuse tracked_record = record&.try(:storage_tracked_record) - return unless tracked_record.present? - return if whitelisted_for_cross_account? + if tracked_record.present? && + !whitelisted_for_cross_account? && + !(record_type == "ActionText::RichText" && name == "embeds") - # Check for existing attachment of this blob in tracked contexts - # Uses Storage::TRACKED_RECORD_TYPES constant to stay generic - existing = ActiveStorage::Attachment - .where(blob_id: blob_id) - .where(record_type: Storage::TRACKED_RECORD_TYPES) - .where.not(id: id) - .exists? + # Check for existing attachment of this blob in tracked contexts + # Uses Storage::TRACKED_RECORD_TYPES constant to stay generic + existing = ActiveStorage::Attachment + .where(blob_id: blob_id) + .where(record_type: Storage::TRACKED_RECORD_TYPES) + .where.not(id: id) + .exists? - if existing - errors.add(:blob_id, "cannot reuse blob in tracked storage context") + if existing + errors.add(:blob_id, "cannot reuse blob in tracked storage context") + end end end diff --git a/config/initializers/active_storage_purge_on_last_attachment.rb b/config/initializers/active_storage_purge_on_last_attachment.rb new file mode 100644 index 000000000..cbe0f0f13 --- /dev/null +++ b/config/initializers/active_storage_purge_on_last_attachment.rb @@ -0,0 +1,40 @@ +# Fizzy-specific override: ActiveStorage's default purge path uses `delete`, +# which skips attachment callbacks. We need `destroy` so storage ledger detaches +# are recorded and reused blobs (ActionText embeds) aren't purged until the +# last attachment is gone. Keep this local to Fizzy; it's not a Rails default. +module ActiveStorage + module PurgeOnLastAttachment + def purge + @purge_mode = :purge + destroy + purge_blob_if_last(:purge) if destroyed? + ensure + @purge_mode = nil + end + + def purge_later + @purge_mode = :purge_later + destroy + purge_blob_if_last(:purge_later) if destroyed? + ensure + @purge_mode = nil + end + + private + def purge_dependent_blob_later + if dependent == :purge_later && !@purge_mode + purge_blob_if_last(:purge_later) + end + end + + def purge_blob_if_last(mode) + if blob && !blob.attachments.exists? + mode == :purge ? blob.purge : blob.purge_later + end + end + end +end + +ActiveSupport.on_load(:active_storage_attachment) do + prepend ActiveStorage::PurgeOnLastAttachment +end diff --git a/script/migrations/backfill-storage-ledger.rb b/script/migrations/backfill-storage-ledger.rb index 5d89342fc..aa3ec793f 100644 --- a/script/migrations/backfill-storage-ledger.rb +++ b/script/migrations/backfill-storage-ledger.rb @@ -10,17 +10,19 @@ # # Safe to re-run: skips attachments that already have entries (by blob_id + recordable). # -# IMPORTANT: Before running in production, verify zero historic blob reuse in tracked contexts: +# OPTIONAL: If you want to enforce no-reuse for direct attachments, verify there are +# no existing violations (ActionText embeds may legitimately reuse blobs): # # ActiveStorage::Attachment # .joins(:blob) # .where(record_type: Storage::TRACKED_RECORD_TYPES) +# .where.not(record_type: "ActionText::RichText") # .where.not(active_storage_blobs: { account_id: Storage::TEMPLATE_ACCOUNT_ID }) # .select(:blob_id) # .group(:blob_id) # .having("COUNT(*) > 1") # .count -# # Should return empty hash if no reuse exists in tracked contexts +# # Should return empty hash if no direct-attachment reuse exists # # If reuse exists (excluding template blobs), fix the data first. class BackfillStorageLedger diff --git a/test/models/storage/no_reuse_test.rb b/test/models/storage/no_reuse_test.rb index 5af07511f..e876fe50c 100644 --- a/test/models/storage/no_reuse_test.rb +++ b/test/models/storage/no_reuse_test.rb @@ -33,6 +33,83 @@ class Storage::NoReuseTest < ActiveSupport::TestCase assert_equal 1, ActiveStorage::Attachment.where(blob_id: blob.id).count end + test "allows reuse for ActionText embeds" do + file = file_fixture("moon.jpg") + blob = ActiveStorage::Blob.create_and_upload! \ + io: file.open, + filename: "embed.jpg", + content_type: "image/jpeg" + + embed_html = ActionText::Attachment.from_attachable(blob).to_html + + card1 = @board.cards.create!(title: "Card 1", creator: users(:david)) + card1.update!(description: "

#{embed_html}

") + card1.reload + + card2 = @board.cards.create!(title: "Card 2", creator: users(:david)) + card2.update!(description: "

#{embed_html}

") + card2.reload + + assert_equal 2, ActiveStorage::Attachment.where( + record_type: "ActionText::RichText", + name: "embeds", + blob_id: blob.id + ).count + end + + test "purge_later does not purge blob when still attached elsewhere" do + file = file_fixture("moon.jpg") + blob = ActiveStorage::Blob.create_and_upload! \ + io: file.open, + filename: "embed.jpg", + content_type: "image/jpeg" + + embed_html = ActionText::Attachment.from_attachable(blob).to_html + + card1 = @board.cards.create!(title: "Card 1", creator: users(:david)) + card1.update!(description: "

#{embed_html}

") + + card2 = @board.cards.create!(title: "Card 2", creator: users(:david)) + card2.update!(description: "

#{embed_html}

") + + attachment = ActiveStorage::Attachment.find_by( + record: card1.rich_text_description, + name: "embeds", + blob_id: blob.id + ) + + assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do + attachment.purge_later + end + + assert ActiveStorage::Blob.exists?(blob.id) + assert_equal 1, ActiveStorage::Attachment.where(blob_id: blob.id).count + end + + test "purge_later enqueues purge when last attachment is removed" do + file = file_fixture("moon.jpg") + blob = ActiveStorage::Blob.create_and_upload! \ + io: file.open, + filename: "embed.jpg", + content_type: "image/jpeg" + + embed_html = ActionText::Attachment.from_attachable(blob).to_html + + card = @board.cards.create!(title: "Card", creator: users(:david)) + card.update!(description: "

#{embed_html}

") + card.reload + + attachment = ActiveStorage::Attachment.find_by( + record: card.rich_text_description, + name: "embeds", + blob_id: blob.id + ) + + assert_enqueued_with job: ActiveStorage::PurgeJob, args: [ blob ] do + attachment.purge_later + end + end + test "rejects cross-account blob attachment" do other_account = Account.create!(name: "Other") other_board = other_account.boards.create!(name: "Other Board", creator: users(:david)) From 99e89606b45da36bd83be41fd636625f877a0e50 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 12 Jan 2026 13:06:30 -0600 Subject: [PATCH 04/22] Keep strong tags words on same line --- saas/app/views/account/settings/_free_plan.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saas/app/views/account/settings/_free_plan.html.erb b/saas/app/views/account/settings/_free_plan.html.erb index dacb7e150..edb5bb57a 100644 --- a/saas/app/views/account/settings/_free_plan.html.erb +++ b/saas/app/views/account/settings/_free_plan.html.erb @@ -10,7 +10,7 @@ <% end %> -

To keep using Fizzy <%= "past #{ number_with_delimiter(Plan.free.card_limit) } cards," unless Current.account.exceeding_storage_limit? %> it’s only <%= format_currency(Plan.paid.price) %>/month for unlimited cards, unlimited users, and <%= storage_to_human_size(Plan.paid.storage_limit) %> of storage.

+

To keep using Fizzy <%= "past #{ number_with_delimiter(Plan.free.card_limit) } cards," unless Current.account.exceeding_storage_limit? %> it’s only <%= format_currency(Plan.paid.price) %>/month for unlimited cards, unlimited users, and <%= storage_to_human_size(Plan.paid.storage_limit) %> of storage.

<%= button_to "Upgrade to #{Plan.paid.name} for #{ format_currency(Plan.paid.price) }/month", account_subscription_path, class: "btn settings-subscription__button txt-medium", form: { data: { turbo: false } } %> From af3fa177aa36a4082bf59c1613c918b466bedbf6 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 12 Jan 2026 13:07:34 -0600 Subject: [PATCH 05/22] Phrasing tweak for exceeding storage limit --- saas/app/views/account/settings/_free_plan.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saas/app/views/account/settings/_free_plan.html.erb b/saas/app/views/account/settings/_free_plan.html.erb index edb5bb57a..ca467d8af 100644 --- a/saas/app/views/account/settings/_free_plan.html.erb +++ b/saas/app/views/account/settings/_free_plan.html.erb @@ -4,7 +4,7 @@ <% elsif Current.account.exceeding_card_limit? %> You’ve used up your <%= number_with_delimiter(Plan.free.card_limit) %> free cards <% elsif Current.account.exceeding_storage_limit? %> - You’ve used up all <%= storage_to_human_size(Plan.free.storage_limit) %> free storage + You’ve used up all <%= storage_to_human_size(Plan.free.storage_limit) %> of free storage <% else %> You’ve used <%= Current.account.billed_cards_count %> free cards out of <%= number_with_delimiter(Plan.free.card_limit) %> <% end %> From 8bb043bef817c6a66a13e025824b2216fb720b4b Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 12 Jan 2026 13:14:37 -0600 Subject: [PATCH 06/22] Move Undo form inside closure message element --- app/assets/stylesheets/card-perma.css | 4 ++++ app/views/cards/container/_closure.html.erb | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/card-perma.css b/app/assets/stylesheets/card-perma.css index 240dadb4b..8d1340c23 100644 --- a/app/assets/stylesheets/card-perma.css +++ b/app/assets/stylesheets/card-perma.css @@ -343,5 +343,9 @@ grid-area: closure-message; margin-block-start: 0.5ch; padding-inline: 1ch; + + form { + display: inline; + } } } diff --git a/app/views/cards/container/_closure.html.erb b/app/views/cards/container/_closure.html.erb index 09b7a4ef4..8aa190c78 100644 --- a/app/views/cards/container/_closure.html.erb +++ b/app/views/cards/container/_closure.html.erb @@ -2,10 +2,12 @@ <% if card.closed? %>
- Completed by <%= card.closed_by.name %> on <%= local_datetime_tag(card.closed_at, style: :shortdate) %>. - <%= button_to card_closure_path(card), method: :delete, class: "btn btn--plain borderless fill-transparent" do %> - Undo - <% end %> + + Completed by <%= card.closed_by.name %> on <%= local_datetime_tag(card.closed_at, style: :shortdate) %>. + <%= button_to card_closure_path(card), method: :delete, class: "btn btn--plain borderless fill-transparent" do %> + Undo + <% end %> +
<% else %> From ca5bca979628f9a81a393023d9148583078e301a Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 12 Jan 2026 13:18:39 -0600 Subject: [PATCH 07/22] Bump boost size up a tick on mobile --- app/assets/stylesheets/comments.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/comments.css b/app/assets/stylesheets/comments.css index a8956a98e..8fef15ebb 100644 --- a/app/assets/stylesheets/comments.css +++ b/app/assets/stylesheets/comments.css @@ -4,7 +4,7 @@ --comment-padding-block: var(--block-space-half); --comment-padding-inline: var(--inline-space-double); --comment-max: 70ch; - --reaction-size: 2rem; + --reaction-size: 2.25rem; display: flex; flex-direction: column; @@ -130,6 +130,10 @@ .comment__edit { background-color: var(--color-ink-lightest); + + &:hover { + z-index: 1; + } } .comment__permalink-title { From 025d70dd130702be957497a5a8a0985095246c5e Mon Sep 17 00:00:00 2001 From: Rosa Gutierrez Date: Wed, 7 Jan 2026 14:13:15 +0100 Subject: [PATCH 08/22] Use separate cache namespaces for each beta instance Otherwise we end up with views including URLs from all the betas, leading to some confusion and problems with the CSP. --- config/cache.yml | 2 +- saas/config/deploy.beta.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config/cache.yml b/config/cache.yml index d1716bb58..302a4f878 100644 --- a/config/cache.yml +++ b/config/cache.yml @@ -1,7 +1,7 @@ default_options: &default_options store_options: max_age: <%= 60.days.to_i %> - namespace: <%= Rails.env %> + namespace: <%= "#{Rails.env}#{"-#{ENV["CACHE_NAMESPACE"]}" if ENV["CACHE_NAMESPACE"]}" %> default_connection: &default_connection database: cache diff --git a/saas/config/deploy.beta.yml b/saas/config/deploy.beta.yml index 14bf89698..d5fce9d32 100644 --- a/saas/config/deploy.beta.yml +++ b/saas/config/deploy.beta.yml @@ -75,6 +75,7 @@ ssh: env: clear: APP_FQDN: beta<%= @beta_number %>.fizzy-beta.com + CACHE_NAMESPACE: <%= @beta_number %> RAILS_ENV: beta RAILS_LOG_LEVEL: fatal # suppress unstructured log lines MYSQL_DATABASE_HOST: fizzy-mysql-primary From df7df74752d6663337558a64ebfd13ab6f48bb5d Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 12 Jan 2026 13:31:36 -0600 Subject: [PATCH 09/22] Brighten mini bubbles in dark mode --- app/assets/stylesheets/card-columns.css | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 6aaacaf8b..0cf3df345 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -749,11 +749,12 @@ .cards--doing.is-collapsed:has(.bubble:not([hidden])) .cards__transition-container { --bubble-color: var(--card-color, oklch(var(--lch-blue-medium))); --bubble-shape: 54% 46% 61% 39% / 57% 49% 51% 43%; + --bubble-opacity: 50%; &:before { background: radial-gradient( - color-mix(in srgb, var(--bubble-color) 8%, var(--color-canvas)) 50%, - color-mix(in srgb, var(--bubble-color) 48%, var(--color-canvas)) 100% + color-mix(in srgb, var(--bubble-color) calc(var(--bubble-opacity) / 5), var(--color-canvas)) 50%, + color-mix(in srgb, var(--bubble-color) var(--bubble-opacity), var(--color-canvas)) 100% ); block-size: 1em; border-radius: var(--bubble-shape); @@ -777,6 +778,22 @@ z-index: -1; } } + + @media (max-width: 639px) { + &.cards__expander-title:before { + display: none; + } + } + + html[data-theme="dark"] & { + --bubble-opacity: 100%; + } + + @media (prefers-color-scheme: dark) { + html:not([data-theme]) & { + --bubble-opacity: 100%; + } + } } /* Card column indicators From 8288e85df6d26a61e554a5913afc617a477d7835 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 12 Jan 2026 13:34:30 -0600 Subject: [PATCH 10/22] More sturated mini bubbles in light mode --- app/assets/stylesheets/card-columns.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 0cf3df345..f60e8da73 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -748,8 +748,8 @@ .cards--maybe.is-collapsed:has(.bubble:not([hidden])) .cards__transition-container, .cards--doing.is-collapsed:has(.bubble:not([hidden])) .cards__transition-container { --bubble-color: var(--card-color, oklch(var(--lch-blue-medium))); + --bubble-opacity: 75%; --bubble-shape: 54% 46% 61% 39% / 57% 49% 51% 43%; - --bubble-opacity: 50%; &:before { background: radial-gradient( From 4dedfce39f9879aeff3089418f5ceb8012a6533b Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 12 Jan 2026 14:00:37 -0600 Subject: [PATCH 11/22] Add dialog manager to events page --- app/views/events/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index 1279e4dac..5e0744d26 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -6,7 +6,7 @@ <% content_for :header do %> <%= render "events/index/add_card_button", user_filtering: @user_filtering %> -

+

<% if @user_filtering.boards.many? %> Activity <%= @user_filtering.filter.boards.any? ? "in" : "across" %> <% else %> From 64fdbf6b8fef564a7d7e47cebb6cf732fe208251 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 12 Jan 2026 15:37:06 -0600 Subject: [PATCH 12/22] Clean up blank slates --- app/assets/stylesheets/blank-slates.css | 51 +++++-------------- app/assets/stylesheets/card-columns.css | 28 +++++++++- app/assets/stylesheets/notifications.css | 2 +- app/assets/stylesheets/search.css | 2 +- .../columns/_empty_placeholder.html.erb | 6 +-- .../boards/show/_filtered_cards.html.erb | 2 +- app/views/cards/index.html.erb | 2 +- app/views/my/menus/_jump.html.erb | 2 +- .../index/_unread_notifications.html.erb | 2 +- .../boards/columns/closeds/show.html.erb | 2 +- .../boards/columns/not_nows/show.html.erb | 2 +- app/views/public/boards/columns/show.html.erb | 2 +- .../boards/columns/streams/show.html.erb | 2 +- app/views/searches/_results.html.erb | 2 +- test/controllers/searches_controller_test.rb | 2 +- 15 files changed, 54 insertions(+), 55 deletions(-) diff --git a/app/assets/stylesheets/blank-slates.css b/app/assets/stylesheets/blank-slates.css index aa221c452..90532c33c 100644 --- a/app/assets/stylesheets/blank-slates.css +++ b/app/assets/stylesheets/blank-slates.css @@ -1,46 +1,21 @@ +/* Styles for the blank slate. To manage when they are shown/hidden, do so in context */ + @layer components { - .blank-slate--drag { - box-shadow: none !important; - color: color-mix(in srgb, var(--card-color) 70%, var(--color-canvas)); - - p { - font-size: var(--text-small); - overflow: hidden; - text-align: center; - text-overflow: ellipsis; - white-space: nowrap; - } - - .cards--maybe & { - background-color: var(--card-bg-color) !important; - } - - .cards--grid & { - display: none; - } - } - - .blank-slate--empty { - border: 2px dashed var(--color-ink-light); + .blank-slate { border-radius: 1ch; + border: 2px dashed var(--color-ink-lighter); color: var(--color-ink-dark); - margin-block-start: 2dvh; - padding: 1ch 2ch; - rotate: -3deg; font-weight: 500; - - .cards:has(.card) & { - display: none; - } + margin-block-start: 2dvh; + padding: 1.5ch 2ch; + rotate: -3deg; } - .blank-slate--filters { - .cards--grid:has(.card) & { - display: none; - } - } - - .cards__list:has(> :not(.blank-slate)) .card--hide-unless-empty { - display: none; + .blank-slate--drag { + background-color: color-mix(in srgb, transparent, var(--card-color) 5%); + border-color: color-mix(in srgb, transparent, var(--card-color) 10%); + color: color-mix(in srgb, transparent, var(--card-color) 75%); + margin-block-start: 0; + rotate: 0deg; } } diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index f60e8da73..4edb06c64 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -255,6 +255,27 @@ } } } + + &:has(.card) { + .blank-slate { + display: none; + } + } + + /* Use the default blank-slate on small viewports since drag-and-drop isn't available */ + [data-controller~="drag-and-drop"] & { + @media (max-width: 639px) { + .blank-slate--drag { + display: none; + } + } + + @media (min-width: 640px) { + .blank-slate--default { + display: none; + } + } + } } .cards__new-column { @@ -315,6 +336,10 @@ inline-size: fit-content; margin: 0 0 0 auto; } + + .blank-slate--drag { + display: none; + } } /* Column Elements @@ -731,7 +756,8 @@ .cards--on-deck { --card-color: var(--color-ink-light) !important; - .card { + .card, + .blank-slate { --card-color: var(--color-card-complete) !important; } diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index ada7324d2..91ff5750d 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -30,7 +30,7 @@ } &:has(.card--notification) { - .notifications-list__empty-message { + .notifications-list__blank-slate { display: none; } } diff --git a/app/assets/stylesheets/search.css b/app/assets/stylesheets/search.css index fcd4563ca..5691ac5e5 100644 --- a/app/assets/stylesheets/search.css +++ b/app/assets/stylesheets/search.css @@ -71,7 +71,7 @@ summary { text-align: start; } - .search__empty { + .search__blank-slate { margin-block: 3em; margin-inline: auto; inline-size: fit-content; diff --git a/app/views/boards/columns/_empty_placeholder.html.erb b/app/views/boards/columns/_empty_placeholder.html.erb index d9da8a896..21e709b84 100644 --- a/app/views/boards/columns/_empty_placeholder.html.erb +++ b/app/views/boards/columns/_empty_placeholder.html.erb @@ -1,4 +1,2 @@ -
-

Drag cards here

-
-
No cards here
+
No cards here
+
Drag cards here
diff --git a/app/views/boards/show/_filtered_cards.html.erb b/app/views/boards/show/_filtered_cards.html.erb index 4e4d8f749..53b25f80f 100644 --- a/app/views/boards/show/_filtered_cards.html.erb +++ b/app/views/boards/show/_filtered_cards.html.erb @@ -3,6 +3,6 @@ <%= with_automatic_pagination :filtered_cards_paginated_container, page do %> <%= render "cards/display/previews", cards: page.records, draggable: true %> <% end %> -
No cards match this filter
+
No cards match this filter
diff --git a/app/views/cards/index.html.erb b/app/views/cards/index.html.erb index a7e4769f5..20bbfac98 100644 --- a/app/views/cards/index.html.erb +++ b/app/views/cards/index.html.erb @@ -23,7 +23,7 @@ <%= with_automatic_pagination :cards_paginated_container, @page do %> <%= render "cards/display/previews", cards: @page.records, draggable: true %> <% end %> -
No cards match this filter
+
No cards match this filter
<% end %> diff --git a/app/views/my/menus/_jump.html.erb b/app/views/my/menus/_jump.html.erb index 1f1ea70c9..f22bedf23 100644 --- a/app/views/my/menus/_jump.html.erb +++ b/app/views/my/menus/_jump.html.erb @@ -26,7 +26,7 @@ <%= yield %> - diff --git a/app/views/notifications/index/_unread_notifications.html.erb b/app/views/notifications/index/_unread_notifications.html.erb index b8b9618d1..4890a4822 100644 --- a/app/views/notifications/index/_unread_notifications.html.erb +++ b/app/views/notifications/index/_unread_notifications.html.erb @@ -5,7 +5,7 @@ <%= button_to "Mark all as read", bulk_reading_path, class: "btn txt-small", form: { data: { turbo: false } }, data: { action: "badge#clear" } %> <% else %> -
+
Nothing new for you
<% end %> diff --git a/app/views/public/boards/columns/closeds/show.html.erb b/app/views/public/boards/columns/closeds/show.html.erb index 21977743e..61ce3887d 100644 --- a/app/views/public/boards/columns/closeds/show.html.erb +++ b/app/views/public/boards/columns/closeds/show.html.erb @@ -18,7 +18,7 @@ <%= render "cards/display/public_previews", cards: @page.records %> <% end %> <% else %> -
No cards here
+
No cards here
<% end %>
<% end %> diff --git a/app/views/public/boards/columns/not_nows/show.html.erb b/app/views/public/boards/columns/not_nows/show.html.erb index 786cb7afb..3dad8bcfe 100644 --- a/app/views/public/boards/columns/not_nows/show.html.erb +++ b/app/views/public/boards/columns/not_nows/show.html.erb @@ -18,7 +18,7 @@ <%= render "cards/display/public_previews", cards: @page.records %> <% end %> <% else %> -
No cards here
+
No cards here
<% end %> <% end %> diff --git a/app/views/public/boards/columns/show.html.erb b/app/views/public/boards/columns/show.html.erb index 6f0d2542f..a8a86451b 100644 --- a/app/views/public/boards/columns/show.html.erb +++ b/app/views/public/boards/columns/show.html.erb @@ -18,7 +18,7 @@ <%= render "cards/display/public_previews", cards: @page.records %> <% end %> <% else %> -
No cards here
+
No cards here
<% end %> <% end %> diff --git a/app/views/public/boards/columns/streams/show.html.erb b/app/views/public/boards/columns/streams/show.html.erb index 4df8c2b12..5566a7955 100644 --- a/app/views/public/boards/columns/streams/show.html.erb +++ b/app/views/public/boards/columns/streams/show.html.erb @@ -18,7 +18,7 @@ <%= render "cards/display/public_previews", cards: @page.records %> <% end %> <% else %> -
No cards here
+
No cards here
<% end %> <% end %> diff --git a/app/views/searches/_results.html.erb b/app/views/searches/_results.html.erb index 602878c65..0b622e95d 100644 --- a/app/views/searches/_results.html.erb +++ b/app/views/searches/_results.html.erb @@ -1,7 +1,7 @@