From 1560b718e36b2b34f011821ef0accf31253611c2 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 5 Nov 2025 09:55:38 -0600 Subject: [PATCH 001/175] Add new containers --- app/views/collections/show/_closed.html.erb | 14 +++++++------ app/views/collections/show/_column.html.erb | 22 +++++++++++--------- app/views/collections/show/_not_now.html.erb | 14 +++++++------ app/views/collections/show/_stream.html.erb | 14 +++++++------ 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/app/views/collections/show/_closed.html.erb b/app/views/collections/show/_closed.html.erb index 383bba6db..98fa7e22b 100644 --- a/app/views/collections/show/_closed.html.erb +++ b/app/views/collections/show/_closed.html.erb @@ -5,10 +5,12 @@ data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" data-drag-and-drop-url="<%= columns_card_drops_closure_path("__id__") %>" > - - <%= render "collections/show/expander", title: "Done", count: collection.cards.closed.count, column_id: "closed-cards" do %> - <%= render "collections/show/menu/expand", column_path: collection_columns_closed_path(collection) %> - <% end %> - - <%= column_frame_tag :closed_column, src: collection_columns_closed_path(collection) %> +
+
+ <%= render "collections/show/expander", title: "Done", count: collection.cards.closed.count, column_id: "closed-cards" do %> + <%= render "collections/show/menu/expand", column_path: collection_columns_closed_path(collection) %> + <% end %> +
+ <%= column_frame_tag :closed_column, src: collection_columns_closed_path(collection) %> +
diff --git a/app/views/collections/show/_column.html.erb b/app/views/collections/show/_column.html.erb index 3a6084b0c..32d3d6a39 100644 --- a/app/views/collections/show/_column.html.erb +++ b/app/views/collections/show/_column.html.erb @@ -7,14 +7,16 @@ data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle turbo:before-stream-render@document->collapsible-columns#restoreState" data-drag-and-drop-url="<%= columns_card_drops_column_path("__id__", column_id: column.id) %>" > - <%= render "collections/show/expander", title: column.name, count: column.cards.active.count, column_id: dom_id(column) do %> - <%= render "collections/show/menu/column", column: column %> - <% end %> - - <%= link_to collection_column_path(column.collection, column), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> - <%= icon_tag "expand", class: "translucent" %> - Expand column - <% end %> - - <%= column_frame_tag dom_id(column, :cards), src: collection_column_path(column.collection, column) %> +
+
+ <%= render "collections/show/expander", title: column.name, count: column.cards.active.count, column_id: dom_id(column) do %> + <%= render "collections/show/menu/column", column: column %> + <% end %> + <%= link_to collection_column_path(column.collection, column), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> + <%= icon_tag "expand", class: "translucent" %> + Expand column + <% end %> +
+ <%= column_frame_tag dom_id(column, :cards), src: collection_column_path(column.collection, column) %> +
diff --git a/app/views/collections/show/_not_now.html.erb b/app/views/collections/show/_not_now.html.erb index c99b9b3af..f98a6ba38 100644 --- a/app/views/collections/show/_not_now.html.erb +++ b/app/views/collections/show/_not_now.html.erb @@ -5,10 +5,12 @@ data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" data-drag-and-drop-url="<%= columns_card_drops_not_now_path("__id__") %>" > - - <%= render "collections/show/expander", title: "Not Now", count: collection.cards.postponed.count, column_id: "not-now" do %> - <%= render "collections/show/menu/expand", column_path: collection_columns_not_now_path(collection) %> - <% end %> - - <%= column_frame_tag :not_now_column, src: collection_columns_not_now_path(collection) %> +
+
+ <%= render "collections/show/expander", title: "Not Now", count: collection.cards.postponed.count, column_id: "not-now" do %> + <%= render "collections/show/menu/expand", column_path: collection_columns_not_now_path(collection) %> + <% end %> +
+ <%= column_frame_tag :not_now_column, src: collection_columns_not_now_path(collection) %> +
diff --git a/app/views/collections/show/_stream.html.erb b/app/views/collections/show/_stream.html.erb index a1985a3f2..eecbe4ee5 100644 --- a/app/views/collections/show/_stream.html.erb +++ b/app/views/collections/show/_stream.html.erb @@ -2,12 +2,14 @@ data-drag-and-drop-target="container" data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" data-drag-and-drop-url="<%= columns_card_drops_stream_path("__id__") %>"> -
-

Maybe?

- <%= link_to collection_columns_stream_path(collection), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> - <%= icon_tag "expand", class: "translucent" %> - Expand column - <% end %> +
+
+

Maybe?

+ <%= link_to collection_columns_stream_path(collection), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> + <%= icon_tag "expand", class: "translucent" %> + Expand column + <% end %> +
<%= render "columns/show/add_card_button", collection: collection %> From 26a20e610b0910a81f57e7e205c325ce34c269a7 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 5 Nov 2025 10:04:35 -0600 Subject: [PATCH 002/175] Add drag and drop colors --- app/assets/stylesheets/card-columns.css | 15 ++++++++++ app/assets/stylesheets/drag_and_drop.css | 7 +++-- app/views/collections/show/_stream.html.erb | 32 +++++++++++---------- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index eebbb74f9..753c90e63 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -91,6 +91,11 @@ } } + &.drag-and-drop__hover-container { + --dnd-bg-color: var(--column-color); + --dnd-border-color: var(--card-color); + } + @media (any-hover: hover) { .card:has(.card__background img:not([src=""])):hover .card__background img:not([src=""]) { filter: blur(3px) brightness(1.2); @@ -99,6 +104,16 @@ } } + .cards__transition-container { + block-size: 100%; + } + + .cards__header { + .cards.is-collapsed & { + block-size: 100%; + } + } + /* The wrapper around the cards used to clip overflow while transitioning. * Also, don't resize cards while transitioning to avoid reflow. */ .cards__list { diff --git a/app/assets/stylesheets/drag_and_drop.css b/app/assets/stylesheets/drag_and_drop.css index 99b3089eb..2ada036b4 100644 --- a/app/assets/stylesheets/drag_and_drop.css +++ b/app/assets/stylesheets/drag_and_drop.css @@ -7,8 +7,11 @@ } .drag-and-drop__hover-container { - background-color: var(--color-selected-light); - outline: 2px dashed var(--color-selected-dark); + --dnd-bg-color: var(--color-selected-light); + --dnd-border-color: var(--color-selected-dark); + + background-color: var(--dnd-bg-color); + outline: 2px dashed var(--dnd-border-color); outline-offset: -2px; transition: background-color 200ms; z-index: 1; diff --git a/app/views/collections/show/_stream.html.erb b/app/views/collections/show/_stream.html.erb index eecbe4ee5..253dcb82d 100644 --- a/app/views/collections/show/_stream.html.erb +++ b/app/views/collections/show/_stream.html.erb @@ -2,21 +2,23 @@ data-drag-and-drop-target="container" data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" data-drag-and-drop-url="<%= columns_card_drops_stream_path("__id__") %>"> -
-
-

Maybe?

- <%= link_to collection_columns_stream_path(collection), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> - <%= icon_tag "expand", class: "translucent" %> - Expand column +
+
+
+

Maybe?

+ <%= link_to collection_columns_stream_path(collection), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> + <%= icon_tag "expand", class: "translucent" %> + Expand column + <% end %> +
+
+ + <%= render "columns/show/add_card_button", collection: collection %> + + <% if page.used? %> + <%= with_automatic_pagination "the-stream", @page do %> + <%= render "collections/columns/list", cards: page.records, draggable: true %> <% end %> -
-
- - <%= render "columns/show/add_card_button", collection: collection %> - - <% if page.used? %> - <%= with_automatic_pagination "the-stream", @page do %> - <%= render "collections/columns/list", cards: page.records, draggable: true %> <% end %> - <% end %> + From b421c48595a13acd26a109f3233c12d2f8ac2f2e Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 5 Nov 2025 10:07:40 -0600 Subject: [PATCH 003/175] Move translate to the translate container --- app/assets/stylesheets/card-columns.css | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 753c90e63..4c4595e6b 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -67,12 +67,6 @@ inline-size: var(--column-width-expanded); position: relative; scroll-snap-align: start; - translate: 0; - transition: translate var(--column-transition-duration) var(--ease-out-overshoot-subtle); - - .no-transitions & { - transition: none; - } &:not(.is-collapsed) { padding-inline: var(--column-padding-expanded); @@ -85,10 +79,6 @@ .card { display: none; } - - &:not(.cards--considering) { - translate: 0 var(--column-width-collapsed); - } } &.drag-and-drop__hover-container { @@ -106,6 +96,16 @@ .cards__transition-container { block-size: 100%; + translate: 0; + transition: translate var(--column-transition-duration) var(--ease-out-overshoot-subtle); + + .is-collapsed:not(.cards--considering) & { + translate: 0 var(--column-width-collapsed); + } + + .no-transitions & { + transition: none; + } } .cards__header { From b0f7c10bf9d76ed5abca36f2d4bd42dcf1f3933f Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 5 Nov 2025 10:10:58 -0600 Subject: [PATCH 004/175] Apply drag and drop effects to transition container --- app/assets/stylesheets/card-columns.css | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 4c4595e6b..1f8c4611e 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -63,15 +63,10 @@ .cards { --column-color: color-mix(in srgb, var(--card-color) 15%, var(--color-canvas)); - border-radius: calc(var(--column-width-collapsed) / 2); inline-size: var(--column-width-expanded); position: relative; scroll-snap-align: start; - &:not(.is-collapsed) { - padding-inline: var(--column-padding-expanded); - } - &.is-collapsed { inline-size: var(--column-width-collapsed); @@ -82,8 +77,8 @@ } &.drag-and-drop__hover-container { - --dnd-bg-color: var(--column-color); - --dnd-border-color: var(--card-color); + --dnd-bg-color: transparent; + --dnd-border-color: transparent; } @media (any-hover: hover) { @@ -96,6 +91,7 @@ .cards__transition-container { block-size: 100%; + border-radius: calc(var(--column-width-collapsed) / 2); translate: 0; transition: translate var(--column-transition-duration) var(--ease-out-overshoot-subtle); @@ -103,6 +99,21 @@ translate: 0 var(--column-width-collapsed); } + .cards:not(.is-collapsed) & { + padding-inline: var(--column-padding-expanded); + } + + .drag-and-drop__hover-container & { + --dnd-bg-color: var(--column-color); + --dnd-border-color: var(--card-color); + + background-color: var(--dnd-bg-color); + outline: 2px dashed var(--dnd-border-color); + outline-offset: -2px; + transition: background-color 200ms; + z-index: 1; + } + .no-transitions & { transition: none; } From ce57313ba332c06e52b287cab4e152e21b4007af Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 5 Nov 2025 10:20:25 -0600 Subject: [PATCH 005/175] Add CSS to show column name on hover --- app/assets/stylesheets/card-columns.css | 23 ++++++++++++++++++++ app/views/collections/show/_closed.html.erb | 1 + app/views/collections/show/_column.html.erb | 1 + app/views/collections/show/_not_now.html.erb | 1 + app/views/collections/show/_stream.html.erb | 1 + 5 files changed, 27 insertions(+) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 1f8c4611e..77718633a 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -79,8 +79,31 @@ &.drag-and-drop__hover-container { --dnd-bg-color: transparent; --dnd-border-color: transparent; + + &.show-column-name-on-hover { + &:after { + color: var(--card-color); + content: attr(data-column-name); + font-size: var(--text-x-small); + font-weight: 500; + line-height: var(--column-width-collapsed); + padding-inline: 1ch; + position: fixed; + text-transform: uppercase; + top: 0; + translate: -50%; + border-radius: 99rem; + } + + &.is-collapsed { + &:after { + writing-mode: vertical-rl; + } + } + } } + @media (any-hover: hover) { .card:has(.card__background img:not([src=""])):hover .card__background img:not([src=""]) { filter: blur(3px) brightness(1.2); diff --git a/app/views/collections/show/_closed.html.erb b/app/views/collections/show/_closed.html.erb index 98fa7e22b..97a67698f 100644 --- a/app/views/collections/show/_closed.html.erb +++ b/app/views/collections/show/_closed.html.erb @@ -2,6 +2,7 @@ data-drag-and-drop-target="container" data-drag-and-strum-target="container" data-collapsible-columns-target="column" + data-column-name="Done" data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" data-drag-and-drop-url="<%= columns_card_drops_closure_path("__id__") %>" > diff --git a/app/views/collections/show/_column.html.erb b/app/views/collections/show/_column.html.erb index 32d3d6a39..7cb1642bb 100644 --- a/app/views/collections/show/_column.html.erb +++ b/app/views/collections/show/_column.html.erb @@ -4,6 +4,7 @@ data-drag-and-strum-target="container" data-collapsible-columns-target="column" data-controller="clicker" + data-column-name="<%= column.name %>" data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle turbo:before-stream-render@document->collapsible-columns#restoreState" data-drag-and-drop-url="<%= columns_card_drops_column_path("__id__", column_id: column.id) %>" > diff --git a/app/views/collections/show/_not_now.html.erb b/app/views/collections/show/_not_now.html.erb index f98a6ba38..a5fe53d05 100644 --- a/app/views/collections/show/_not_now.html.erb +++ b/app/views/collections/show/_not_now.html.erb @@ -2,6 +2,7 @@ data-collapsible-columns-target="column" data-drag-and-drop-target="container" data-drag-and-strum-target="container" + data-column-name="Not Now" data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" data-drag-and-drop-url="<%= columns_card_drops_not_now_path("__id__") %>" > diff --git a/app/views/collections/show/_stream.html.erb b/app/views/collections/show/_stream.html.erb index 253dcb82d..93980b991 100644 --- a/app/views/collections/show/_stream.html.erb +++ b/app/views/collections/show/_stream.html.erb @@ -1,5 +1,6 @@
">
From 623d2468efc217224ca0f70a71ad4a27b572709a Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 5 Nov 2025 10:42:31 -0600 Subject: [PATCH 006/175] Add intersection observer to only show when title isn't visible --- app/assets/stylesheets/card-columns.css | 11 +++++-- .../collapsible_columns_controller.js | 31 +++++++++++++++++-- app/views/collections/show/_columns.html.erb | 8 ++++- app/views/collections/show/_expander.html.erb | 2 +- app/views/collections/show/_stream.html.erb | 2 +- .../public/collections/show/_stream.html.erb | 2 +- 6 files changed, 47 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 77718633a..945700f35 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -80,9 +80,8 @@ --dnd-bg-color: transparent; --dnd-border-color: transparent; - &.show-column-name-on-hover { + &.is-off-screen { &:after { - color: var(--card-color); content: attr(data-column-name); font-size: var(--text-x-small); font-weight: 500; @@ -92,7 +91,6 @@ text-transform: uppercase; top: 0; translate: -50%; - border-radius: 99rem; } &.is-collapsed { @@ -100,6 +98,13 @@ writing-mode: vertical-rl; } } + + &:not(.is-collapsed) { + &:after { + background-color: var(--column-color); + inline-size: calc(var(--column-width-expanded) - 4px); /* make room for the dnd border */ + } + } } } diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index 0a87e47d5..3d9c8f412 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -2,8 +2,8 @@ import { Controller } from "@hotwired/stimulus" import { nextFrame, debounce } from "helpers/timing_helpers"; export default class extends Controller { - static classes = [ "collapsed", "noTransitions" ] - static targets = [ "column", "button" ] + static classes = [ "collapsed", "noTransitions", "titleNotVisible" ] + static targets = [ "column", "button", "title" ] static values = { collection: String } @@ -14,6 +14,14 @@ export default class extends Controller { async connect() { await this.#restoreColumnsDisablingTransitions() + this.#setupIntersectionObserver() + } + + disconnect() { + if (this._intersectionObserver) { + this._intersectionObserver.disconnect() + this._intersectionObserver = null + } } toggle({ target }) { @@ -106,4 +114,23 @@ export default class extends Controller { #localStorageKeyFor(column) { return `expand-${this.collectionValue}-${column.getAttribute("id")}` } + + #setupIntersectionObserver() { + if (typeof IntersectionObserver === "undefined") return + if (this._intersectionObserver) this._intersectionObserver.disconnect() + + this._intersectionObserver = new IntersectionObserver(entries => { + entries.forEach(entry => { + const title = entry.target + const column = title.closest('[data-collapsible-columns-target="column"]') + + if (!column) return + + const offscreen = entry.intersectionRatio === 0 + column.classList.toggle(this.titleNotVisibleClass, offscreen) + }) + }, { threshold: [0] }) + + this.titleTargets.forEach(title => this._intersectionObserver.observe(title)) + } } diff --git a/app/views/collections/show/_columns.html.erb b/app/views/collections/show/_columns.html.erb index 27912c09d..a52a308dd 100644 --- a/app/views/collections/show/_columns.html.erb +++ b/app/views/collections/show/_columns.html.erb @@ -8,7 +8,13 @@ dragenter->drag-and-strum#dragEnter drop->drag-and-drop#drop dragend->drag-and-drop#dragEnd" } do %> -
+
<%= render "collections/show/not_now", collection: collection %>
diff --git a/app/views/collections/show/_expander.html.erb b/app/views/collections/show/_expander.html.erb index 91e6466da..ea58f061f 100644 --- a/app/views/collections/show/_expander.html.erb +++ b/app/views/collections/show/_expander.html.erb @@ -1,6 +1,6 @@ <%= yield if block_given? %> diff --git a/app/views/collections/show/_stream.html.erb b/app/views/collections/show/_stream.html.erb index 93980b991..6e19774f2 100644 --- a/app/views/collections/show/_stream.html.erb +++ b/app/views/collections/show/_stream.html.erb @@ -6,7 +6,7 @@
-

Maybe?

+

Maybe?

<%= link_to collection_columns_stream_path(collection), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> <%= icon_tag "expand", class: "translucent" %> Expand column diff --git a/app/views/public/collections/show/_stream.html.erb b/app/views/public/collections/show/_stream.html.erb index fc0d532a8..4fc065e78 100644 --- a/app/views/public/collections/show/_stream.html.erb +++ b/app/views/public/collections/show/_stream.html.erb @@ -1,7 +1,7 @@
-

Maybe?

+

Maybe?

<% if page.used? %> From 6713a97cc7bcabcb4012393bd0db0b3cc896ee45 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 5 Nov 2025 11:05:33 -0600 Subject: [PATCH 007/175] Add hover effect back to Maybe column --- app/assets/stylesheets/card-columns.css | 6 ++---- app/views/collections/show/_stream.html.erb | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 945700f35..8089110b0 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -527,11 +527,9 @@ /* ------------------------------------------------------------------------ */ .cards--considering { - position: relative; + --card-color: oklch(var(--lch-blue-medium)); - @media (min-width: 640px) { - padding-inline: var(--cards-gap); - } + position: relative; .card { --avatar-size: 2.75em; diff --git a/app/views/collections/show/_stream.html.erb b/app/views/collections/show/_stream.html.erb index 6e19774f2..1fd846bdc 100644 --- a/app/views/collections/show/_stream.html.erb +++ b/app/views/collections/show/_stream.html.erb @@ -3,7 +3,7 @@ data-column-name="Maybe?" data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" data-drag-and-drop-url="<%= columns_card_drops_stream_path("__id__") %>"> -
+

Maybe?

From 3e7cd204db8bce832aef8ee85b38f1147014bb62 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 5 Nov 2025 11:14:30 -0600 Subject: [PATCH 008/175] Add title to Maybe column --- .../collapsible_columns_controller.js | 4 +-- app/views/collections/show/_columns.html.erb | 32 ++++++++----------- .../public/collections/show/_stream.html.erb | 2 +- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index 3d9c8f412..2b6c24e71 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -122,8 +122,8 @@ export default class extends Controller { this._intersectionObserver = new IntersectionObserver(entries => { entries.forEach(entry => { const title = entry.target - const column = title.closest('[data-collapsible-columns-target="column"]') - + const column = title.closest(".cards") + if (!column) return const offscreen = entry.intersectionRatio === 0 diff --git a/app/views/collections/show/_columns.html.erb b/app/views/collections/show/_columns.html.erb index a52a308dd..79a1cf4ec 100644 --- a/app/views/collections/show/_columns.html.erb +++ b/app/views/collections/show/_columns.html.erb @@ -1,32 +1,28 @@ -<%= tag.div data: { - controller: "drag-and-drop drag-and-strum", +<%= tag.div class: "card-columns", data: { + controller: "drag-and-drop drag-and-strum collapsible-columns", drag_and_drop_dragged_item_class: "drag-and-drop__dragged-item", drag_and_drop_hover_container_class: "drag-and-drop__hover-container", + collapsible_columns_collection_value: collection.id, + collapsible_columns_collapsed_class: "is-collapsed", + collapsible_columns_title_not_visible_class: "is-off-screen", + collapsible_columns_no_transitions_class: "no-transitions", action: " dragstart->drag-and-drop#dragStart dragover->drag-and-drop#dragOver dragenter->drag-and-strum#dragEnter drop->drag-and-drop#drop dragend->drag-and-drop#dragEnd" } do %> -
-
- <%= render "collections/show/not_now", collection: collection %> -
+
+ <%= render "collections/show/not_now", collection: collection %> +
- <%= render "collections/show/stream", collection: collection, page: page %> + <%= render "collections/show/stream", collection: collection, page: page %> -
- <%= render partial: "collections/show/column", collection: collection.columns.sorted, cached: ->(column){ [ column, column.leftmost?, column.rightmost? ] } %> - <%= render "collections/show/closed", collection: collection %> +
+ <%= render partial: "collections/show/column", collection: collection.columns.sorted, cached: ->(column){ [ column, column.leftmost?, column.rightmost? ] } %> + <%= render "collections/show/closed", collection: collection %> - <%= render "collections/show/menu/columns", collection: collection %> -
+ <%= render "collections/show/menu/columns", collection: collection %>
<% end %> diff --git a/app/views/public/collections/show/_stream.html.erb b/app/views/public/collections/show/_stream.html.erb index 4fc065e78..94df5f07d 100644 --- a/app/views/public/collections/show/_stream.html.erb +++ b/app/views/public/collections/show/_stream.html.erb @@ -1,4 +1,4 @@ -

Maybe?

From 72a1cfc0887ec5604ca0f35568083bdf39cf464b Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Wed, 5 Nov 2025 11:05:22 -0500 Subject: [PATCH 009/175] dep: update Rails and AR::Tenanted Note that the query logs in this version of AR::Tenanted have changed, to use standard Rails query log tags, which I have enabled. Note that enabling Rails query log tags disables prepared statements. I don't think turning off prepared statements will impact our performance, but I'll verify that post-deploy (we have dashboards for a reason). --- Gemfile.lock | 8 ++++---- config/application.rb | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index cfbb22c7b..a600a6edd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,8 @@ GIT remote: https://github.com/basecamp/activerecord-tenanted - revision: aaa4a3473eab400c4c8a6ca05a7207578d3ade53 + revision: e409f75dc2ea1c3355e98feb22b76b3cc0a01fe0 specs: - activerecord-tenanted (0.5.0) + activerecord-tenanted (0.6.0) activerecord (>= 8.1.beta) railties (>= 8.1.beta) zeitwerk @@ -36,7 +36,7 @@ GIT GIT remote: https://github.com/rails/rails.git - revision: d3d17c297a70d3e255a34693a1fb0074712e9930 + revision: bc41ad696a76a151db56259938dccf64ef87b501 branch: main specs: actioncable (8.2.0.alpha) @@ -537,7 +537,7 @@ GEM unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.1.0) - uri (1.1.0) + uri (1.1.1) useragent (0.16.11) vcr (6.3.1) base64 diff --git a/config/application.rb b/config/application.rb index 6fe42e9ef..b1a7da2be 100644 --- a/config/application.rb +++ b/config/application.rb @@ -29,5 +29,9 @@ module Fizzy # use routes for error pages instead of static files config.exceptions_app = self.routes + + # include the tenant in query logs + config.active_record.query_log_tags_enabled = true + config.active_record.query_log_tags = [ :tenant ] end end From 05eba715528e02f7758bceab9e481707c2e2a7dc Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Wed, 5 Nov 2025 19:19:26 +0100 Subject: [PATCH 010/175] Order of concerns --- app/models/board.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/board.rb b/app/models/board.rb index 9e259d652..71ff693fb 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -1,5 +1,5 @@ class Board < ApplicationRecord - include AutoPostponing, Accessible, Broadcastable, Cards, Entropic, Filterable, Publishable, Triageable + include Accessible, AutoPostponing, Broadcastable, Cards, Entropic, Filterable, Publishable, Triageable belongs_to :creator, class_name: "User", default: -> { Current.user } From 52c49d99c036bb64c4b9d8c8ccb4f51f9c75e280 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 5 Nov 2025 12:47:27 -0600 Subject: [PATCH 011/175] Cleanup after merging from Main --- app/views/boards/show/_columns.html.erb | 26 +++++++++++++------------ app/views/boards/show/_stream.html.erb | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/views/boards/show/_columns.html.erb b/app/views/boards/show/_columns.html.erb index a2a406aff..5223b9254 100644 --- a/app/views/boards/show/_columns.html.erb +++ b/app/views/boards/show/_columns.html.erb @@ -1,26 +1,28 @@ -<%= tag.div data: { - controller: "drag-and-drop drag-and-strum", +<%= tag.div class: "card-columns", data: { + controller: "collapsible-columns drag-and-drop drag-and-strum", drag_and_drop_dragged_item_class: "drag-and-drop__dragged-item", drag_and_drop_hover_container_class: "drag-and-drop__hover-container", + collapsible_columns_board_value: board.id, + collapsible_columns_collapsed_class: "is-collapsed", + collapsible_columns_no_transitions_class: "no-transitions", + collapsible_columns_title_not_visible_class: "is-off-screen", action: " dragstart->drag-and-drop#dragStart dragover->drag-and-drop#dragOver dragenter->drag-and-strum#dragEnter drop->drag-and-drop#drop dragend->drag-and-drop#dragEnd" } do %> -
-
- <%= render "boards/show/not_now", board: board %> -
+
+ <%= render "boards/show/not_now", board: board %> +
- <%= render "boards/show/stream", board: board, page: page %> + <%= render "boards/show/stream", board: board, page: page %> -
- <%= render partial: "boards/show/column", collection: board.columns.sorted, cached: ->(column){ [ column, column.leftmost?, column.rightmost? ] } %> - <%= render "boards/show/closed", board: board %> +
+ <%= render partial: "boards/show/column", collection: board.columns.sorted, cached: ->(column){ [ column, column.leftmost?, column.rightmost? ] } %> + <%= render "boards/show/closed", board: board %> - <%= render "boards/show/menu/columns", board: board %> -
+ <%= render "boards/show/menu/columns", board: board %>
<% end %> diff --git a/app/views/boards/show/_stream.html.erb b/app/views/boards/show/_stream.html.erb index ed3aba323..5d5f6508f 100644 --- a/app/views/boards/show/_stream.html.erb +++ b/app/views/boards/show/_stream.html.erb @@ -6,7 +6,7 @@
-

Maybe?

+

Maybe?

<%= link_to board_columns_stream_path(board), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> <%= icon_tag "expand", class: "translucent" %> Expand column From d035ae89b28b772880cb60e0e919eeb9e26b3fa2 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 5 Nov 2025 13:11:46 -0600 Subject: [PATCH 012/175] Reintroduce a more modest content margin --- app/assets/stylesheets/rich-text-content.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/rich-text-content.css b/app/assets/stylesheets/rich-text-content.css index 72cba909b..2e5090592 100644 --- a/app/assets/stylesheets/rich-text-content.css +++ b/app/assets/stylesheets/rich-text-content.css @@ -1,13 +1,13 @@ @layer components { .rich-text-content { - --block-margin: 0; + --block-margin: 0.5lh; :where(h1, h2, h3, h4, h5, h6) { display: block; font-weight: 800; letter-spacing: -0.02ch; line-height: 1.1; - margin-block: var(--block-margin); + margin-block: 0 var(--block-margin); overflow-wrap: break-word; text-wrap: balance; } From ba1ca38f9b9a4ae646ed009babe40046647e5297 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 5 Nov 2025 13:38:00 -0600 Subject: [PATCH 013/175] Improve spacing, color, and click area for dividers --- app/assets/stylesheets/rich-text-content.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/rich-text-content.css b/app/assets/stylesheets/rich-text-content.css index 2e5090592..1fa0c0759 100644 --- a/app/assets/stylesheets/rich-text-content.css +++ b/app/assets/stylesheets/rich-text-content.css @@ -62,14 +62,17 @@ } :where(hr) { + color: currentColor; block-size: 0; - border-block-end: 1px solid currentColor; + border: none; + border-block-end: 2px solid currentColor; inline-size: 20%; - margin: var(--block-margin) 0; + margin: calc(var(--block-margin) * 2) 0; } .horizontal-divider { - margin-inline: 0; + margin: var(--block-margin) 0; + padding-block: var(--block-margin); } .lexxy-content__strikethrough { From b78ca68b2bbcdd0dfc900fc819b70096aa8fb2ca Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Wed, 5 Nov 2025 21:29:57 +0100 Subject: [PATCH 014/175] Restore streams since we rely on those for deleting boosts https://app.fizzy.do/5986089/cards/2811 --- app/models/card.rb | 4 ++-- app/models/card/broadcastable.rb | 7 ------- app/views/cards/show.html.erb | 1 + 3 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 app/models/card/broadcastable.rb diff --git a/app/models/card.rb b/app/models/card.rb index 65c7cab18..859f4b1a7 100644 --- a/app/models/card.rb +++ b/app/models/card.rb @@ -1,6 +1,6 @@ class Card < ApplicationRecord - include Assignable, Attachments, Broadcastable, Closeable, Colored, Entropic, - Eventable, Golden, Mentions, Multistep, Pinnable, Postponable, Promptable, + include Assignable, Attachments, Closeable, Colored, Entropic, Eventable, + Golden, Mentions, Multistep, Pinnable, Postponable, Promptable, Readable, Searchable, Stallable, Statuses, Taggable, Triageable, Watchable belongs_to :board, touch: true diff --git a/app/models/card/broadcastable.rb b/app/models/card/broadcastable.rb deleted file mode 100644 index aa24b776c..000000000 --- a/app/models/card/broadcastable.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Card::Broadcastable - extend ActiveSupport::Concern - - included do - broadcasts_refreshes - end -end diff --git a/app/views/cards/show.html.erb b/app/views/cards/show.html.erb index b96a3a7cb..596006ce8 100644 --- a/app/views/cards/show.html.erb +++ b/app/views/cards/show.html.erb @@ -11,6 +11,7 @@
<% end %> +<%= turbo_stream_from @card %> <%= turbo_stream_from @card, :activity %>
From 90a139a4863fdc7569faacfd445632e142183e94 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Wed, 5 Nov 2025 17:31:10 -0500 Subject: [PATCH 015/175] Use the rubygems-hosted beamer-rails gem --- Gemfile | 4 +--- Gemfile.lock | 26 +++++++++++--------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/Gemfile b/Gemfile index efd0166b4..17e33abd2 100644 --- a/Gemfile +++ b/Gemfile @@ -19,9 +19,7 @@ gem "solid_cache", "~> 1.0" gem "solid_queue", "~> 1.2" gem "sqlite3", ">= 2.0" gem "thruster", require: false -source "https://e95ae463b12de3f204526a44650f6ae0@gems.stanko.io/private" do - gem "beamer-rails", "~> 0.1.0.beta4" -end +gem "beamer-rails", "~> 0.1.0.beta4" # Features gem "bcrypt", "~> 3.1.7" diff --git a/Gemfile.lock b/Gemfile.lock index a600a6edd..78c3baabc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -143,20 +143,6 @@ PATH rails (>= 8.1.0.beta1) rails_structured_logging -GEM - remote: https://gems.stanko.io/private/ - specs: - beamer-rails (0.1.0.beta4-arm64-darwin) - sqlite3 (>= 2.0) - beamer-rails (0.1.0.beta4-arm64-linux-gnu) - sqlite3 (>= 2.0) - beamer-rails (0.1.0.beta4-x86_64-darwin) - sqlite3 (>= 2.0) - beamer-rails (0.1.0.beta4-x86_64-linux-gnu) - sqlite3 (>= 2.0) - beamer-rails (0.1.0.beta4-x86_64-linux-musl) - sqlite3 (>= 2.0) - GEM remote: https://rubygems.org/ specs: @@ -198,6 +184,16 @@ GEM base64 (0.3.0) bcrypt (3.1.20) bcrypt_pbkdf (1.1.1) + beamer-rails (0.1.0.beta4-arm64-darwin) + sqlite3 (>= 2.0) + beamer-rails (0.1.0.beta4-arm64-linux-gnu) + sqlite3 (>= 2.0) + beamer-rails (0.1.0.beta4-x86_64-darwin) + sqlite3 (>= 2.0) + beamer-rails (0.1.0.beta4-x86_64-linux-gnu) + sqlite3 (>= 2.0) + beamer-rails (0.1.0.beta4-x86_64-linux-musl) + sqlite3 (>= 2.0) benchmark (0.5.0) bigdecimal (3.3.1) bindex (0.8.1) @@ -604,7 +600,7 @@ DEPENDENCIES autotuner aws-sdk-s3 bcrypt (~> 3.1.7) - beamer-rails (~> 0.1.0.beta4)! + beamer-rails (~> 0.1.0.beta4) benchmark bootsnap brakeman From 52bf4297723d6b211a37429b9826b420d925c3be Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 5 Nov 2025 15:32:41 -0600 Subject: [PATCH 016/175] Add some ordering words to help the flow --- app/models/account/seeder.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/models/account/seeder.rb b/app/models/account/seeder.rb index 991877833..bd69f6065 100644 --- a/app/models/account/seeder.rb +++ b/app/models/account/seeder.rb @@ -31,49 +31,49 @@ class Account::Seeder playground = Board.create! name: "Playground", creator: creator, all_access: true # Cards - playground.cards.create! creator: creator, title: "Watch this: Fizzy orientation video", status: "published", description: <<~HTML + playground.cards.create! creator: creator, title: "Finally, watch this Fizzy orientation video", status: "published", description: <<~HTML

There’s a whole lot more you can do in Fizzy. In the video below 37signals founder and CEO, Jason Fried, will walk you through the basics in just 8 minutes.


HTML - playground.cards.create! creator: creator, title: "Grab the invite link to invite someone else", status: "published", description: <<~HTML + playground.cards.create! creator: creator, title: "Now, grab the invite link to invite someone else", status: "published", description: <<~HTML

Open Fizzy menu, select “+ Add people”, then copy the invite link. You can give this link to someone else so they can make an login for themselves in your account.


HTML - playground.cards.create! creator: creator, title: "Head back home to check out activity", status: "published", description: <<~HTML + playground.cards.create! creator: creator, title: "Then, head back home to check out activity", status: "published", description: <<~HTML

Hit “1” or pull down the Fizzy menu and select “Home”.


HTML - playground.cards.create! creator: creator, title: "Check out all cards assigned to you", status: "published", description: <<~HTML + playground.cards.create! creator: creator, title: "Now,check out all cards assigned to you", status: "published", description: <<~HTML

Pull down the Fizzy menu at the top of the screen, and select “Assigned to me” or just hit “3” on your keyboard any time.


HTML - playground.cards.create! creator: creator, title: "Open the Fizzy menu", status: "published", description: <<~HTML + playground.cards.create! creator: creator, title: "Then, open the Fizzy menu", status: "published", description: <<~HTML

The Fizzy menu is how you get around the app. Click “Fizzy” at the top of the screen or hit the “J” key on your keyboard to pop it open.


HTML - playground.cards.create! creator: creator, title: "Assign this card to yourself", status: "published", description: <<~HTML + playground.cards.create! creator: creator, title: "Next, assign this card to yourself", status: "published", description: <<~HTML

Click the little head with the + next to it, pick yourself.


HTML - playground.cards.create! creator: creator, title: "Tag this card “Design” then move it to YES", status: "published", description: <<~HTML + playground.cards.create! creator: creator, title: "Now, tag this card “Design” then move it to YES", status: "published", description: <<~HTML

Click the little Tag icon, type “design”, then “Create tag”. Then, move the card to the new “YES” column you created in the previous step.


HTML - playground.cards.create! creator: creator, title: "Make two more columns", status: "published", description: <<~HTML + playground.cards.create! creator: creator, title: "Next, make two more columns", status: "published", description: <<~HTML
  1. Make one called "Yes"
  2. Make another called "Working on"
  3. @@ -86,13 +86,13 @@ class Account::Seeder HTML - playground.cards.create! creator: creator, title: "Move this card to NOT NOW", status: "published", description: <<~HTML + playground.cards.create! creator: creator, title: "Second, move this card to NOT NOW", status: "published", description: <<~HTML

    You can either select “NOT NOW” over in the sidebar, or you can go back out to the board view and drag this card into the “NOT NOW” column on the left side.


    HTML - playground.cards.create! creator: creator, title: "Rename this card", status: "published", description: <<~HTML + playground.cards.create! creator: creator, title: "First, rename this card", status: "published", description: <<~HTML
    1. Click the title and you can rename the card, change the description, or add more information to the card.
    2. Then, hit "Mark as Done" at the bottom of the card.
    3. From 637621518a254ff1434f301d5a460d4391471a30 Mon Sep 17 00:00:00 2001 From: Fernando Olivares Date: Wed, 5 Nov 2025 19:57:33 -0600 Subject: [PATCH 017/175] Split event_action_sentence into *_tag and *_text --- app/helpers/events_helper.rb | 142 +++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 63 deletions(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 992463740..d9ad27613 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -55,19 +55,21 @@ module EventsHelper tag.div class: "events__time-block", style: "grid-area: #{row}/#{col}", & end - def event_action_sentence(event) - if event.action.comment_created? - comment_event_action_sentence(event) - else - card_event_action_sentence(event) - end + def event_action_sentence_tag(event) + creator = event_creator_name_tag(event) + card = event.action.comment_created? ? event.eventable.card : event.eventable + card_title = event_card_title(card) + event_action_sentence(creator, card_title, event).html_safe end - def comment_event_action_sentence(event) - "#{event_creator_name(event)} commented on #{event_card_title(event.eventable.card)}".html_safe + def event_action_sentence_text(event) + creator = event_creator_name_text(event) + card = event.action.comment_created? ? event.eventable.card : event.eventable + card_title = card.title + event_action_sentence(creator, card_title, event) end - def event_creator_name(event) + def event_creator_name_tag(event) tag.span data: { creator_id: event.creator.id } do safe_join([ tag.span("You", data: { only_visible_to_you: true }), @@ -76,63 +78,77 @@ module EventsHelper end end - def card_event_action_sentence(event) - card = event.eventable - - case event.action - when "card_assigned" - if event.assignees.include?(Current.user) - "#{event_creator_name(event)} will handle #{event_card_title(card)}" - else - "#{event_creator_name(event)} assigned #{h event.assignees.pluck(:name).to_sentence} to #{event_card_title(card)}" - end - when "card_unassigned" - "#{event_creator_name(event)} unassigned #{h(event.assignees.include?(Current.user) ? "yourself" : event.assignees.pluck(:name).to_sentence)} from #{event_card_title(card)}" - when "card_published" - "#{event_creator_name(event)} added #{event_card_title(card)}" - when "card_closed" - "#{event_creator_name(event)} moved #{event_card_title(card)} to “Done”" - when "card_reopened" - "#{event_creator_name(event)} reopened #{event_card_title(card)}" - when "card_postponed" - "#{event_creator_name(event)} moved #{event_card_title(card)} to “Not Now”" - when "card_auto_postponed" - "#{event_card_title(card)} was closed as “Not Now” due to inactivity" - when "card_resumed" - "#{event_creator_name(event)} resumed #{event_card_title(card)}" - when "card_title_changed" - "#{event_creator_name(event)} renamed #{event_card_title(card)} (was: “#{h event.particulars.dig('particulars', 'old_title')}”)" - when "card_board_changed", "card_collection_changed" - "#{event_creator_name(event)} moved #{event_card_title(card)} to “#{h (event.particulars.dig('particulars', 'new_board') || event.particulars.dig('particulars', 'new_collection'))}”" - when "card_triaged" - "#{event_creator_name(event)} moved #{event_card_title(card)} to “#{h event.particulars.dig('particulars', 'column')}”" - when "card_sent_back_to_triage" - "#{event_creator_name(event)} moved #{event_card_title(card)} back to “Maybe?”" - end&.html_safe + def event_creator_name_text(event) + event.creator == Current.user ? "You" : h(event.creator.name) end - def event_card_title(card) - tag.span card.title, class: "txt-underline" - end - - def event_due_date(event) - event.particulars.dig("particulars", "due_date").to_date.strftime("%B %-d") - end - - def event_action_icon(event) - case event.action - when "card_assigned" - "assigned" - when "card_unassigned" - "minus" - when "comment_created" - "comment" - when "card_title_changed" - "rename" - when "card_board_changed" - "move" + def event_action_sentence(event_creator, event_card_title, event) + if event.action.comment_created? + comment_event_action_sentence(event_creator, event_card_title, event) else - "person" + card_event_action_sentence(event_creator, event_card_title, event) end end + + def comment_event_action_sentence(event_creator, event_card_title, event) + "#{event_creator} commented on #{event_card_title}" + end + + def card_event_action_sentence(event_creator, event_card_title, event) + case event.action + when "card_assigned" + if event.assignees.include?(Current.user) + "#{event_creator} will handle #{event_card_title}" + else + "#{event_creator} assigned #{h event.assignees.pluck(:name).to_sentence} to #{event_card_title}" + end + when "card_unassigned" + "#{event_creator} unassigned #{h(event.assignees.include?(Current.user) ? "yourself" : event.assignees.pluck(:name).to_sentence)} from #{event_card_title}" + when "card_published" + "#{event_creator} added #{event_card_title}" + when "card_closed" + "#{event_creator} moved #{event_card_title} to “Done”" + when "card_reopened" + "#{event_creator} reopened #{event_card_title}" + when "card_postponed" + "#{event_creator} moved #{event_card_title} to “Not Now”" + when "card_auto_postponed" + "#{event_card_title} was closed as “Not Now” due to inactivity" + when "card_resumed" + "#{event_creator} resumed #{event_card_title}" + when "card_title_changed" + "#{event_creator} renamed #{event_card_title} (was: “#{h event.particulars.dig('particulars', 'old_title')}”)" + when "card_board_changed", "card_collection_changed" + "#{event_creator} moved #{event_card_title} to “#{h (event.particulars.dig('particulars', 'new_board') || event.particulars.dig('particulars', 'new_collection'))}”" + when "card_triaged" + "#{event_creator} moved #{event_card_title} to “#{h event.particulars.dig('particulars', 'column')}”" + when "card_sent_back_to_triage" + "#{event_creator} moved #{event_card_title} back to “Maybe?”" + end + end + + def event_card_title(card) + tag.span card.title, class: "txt-underline" + end + + def event_due_date(event) + event.particulars.dig("particulars", "due_date").to_date.strftime("%B %-d") + end + + def event_action_icon(event) + case event.action + when "card_assigned" + "assigned" + when "card_unassigned" + "minus" + when "comment_created" + "comment" + when "card_title_changed" + "rename" + when "card_board_changed" + "move" + else + "person" + end + end end From de0ae43dcdf5309b3d4de255aded3096af087288 Mon Sep 17 00:00:00 2001 From: Fernando Olivares Date: Wed, 5 Nov 2025 19:58:35 -0600 Subject: [PATCH 018/175] Use event action sentence text in webhook --- app/views/webhooks/event.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/webhooks/event.html.erb b/app/views/webhooks/event.html.erb index 91500a17b..a9835c21e 100644 --- a/app/views/webhooks/event.html.erb +++ b/app/views/webhooks/event.html.erb @@ -1,4 +1,4 @@ -<%= event_action_sentence(@event) %> +<%= event_action_sentence_text(@event) %> <% if @event.eventable %> <%= link_to "↗︎", polymorphic_url(@event.eventable) %> <% end %> From 6aefab2396a74adabaf3f62372fe3309915ca891 Mon Sep 17 00:00:00 2001 From: Fernando Olivares Date: Wed, 5 Nov 2025 19:58:42 -0600 Subject: [PATCH 019/175] Use event action sentence tag in layout --- app/views/events/event/_layout.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/events/event/_layout.html.erb b/app/views/events/event/_layout.html.erb index 0fc767862..1cd1e6831 100644 --- a/app/views/events/event/_layout.html.erb +++ b/app/views/events/event/_layout.html.erb @@ -32,7 +32,7 @@
      - <%= event_action_sentence(event) %> + <%= event_action_sentence_tag(event) %> <%= yield %> From c926d7ad6c145974c4a95e7715663d7bd2f2ca87 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 6 Nov 2025 10:56:07 +0100 Subject: [PATCH 020/175] Extract description object to remove the helper salad Lots of repetition with the helpers and params --- app/helpers/events_helper.rb | 106 +++-------------------- app/models/event.rb | 4 + app/models/event/description.rb | 108 ++++++++++++++++++++++++ app/views/events/event/_layout.html.erb | 2 +- app/views/webhooks/event.html.erb | 2 +- 5 files changed, 127 insertions(+), 95 deletions(-) create mode 100644 app/models/event/description.rb diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index d9ad27613..94e6990d2 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -55,100 +55,20 @@ module EventsHelper tag.div class: "events__time-block", style: "grid-area: #{row}/#{col}", & end - def event_action_sentence_tag(event) - creator = event_creator_name_tag(event) - card = event.action.comment_created? ? event.eventable.card : event.eventable - card_title = event_card_title(card) - event_action_sentence(creator, card_title, event).html_safe - end - - def event_action_sentence_text(event) - creator = event_creator_name_text(event) - card = event.action.comment_created? ? event.eventable.card : event.eventable - card_title = card.title - event_action_sentence(creator, card_title, event) - end - - def event_creator_name_tag(event) - tag.span data: { creator_id: event.creator.id } do - safe_join([ - tag.span("You", data: { only_visible_to_you: true }), - tag.span(h(event.creator.name), data: { only_visible_to_others: true }) - ]) - end - end - - def event_creator_name_text(event) - event.creator == Current.user ? "You" : h(event.creator.name) - end - - def event_action_sentence(event_creator, event_card_title, event) - if event.action.comment_created? - comment_event_action_sentence(event_creator, event_card_title, event) + def event_action_icon(event) + case event.action + when "card_assigned" + "assigned" + when "card_unassigned" + "minus" + when "comment_created" + "comment" + when "card_title_changed" + "rename" + when "card_board_changed" + "move" else - card_event_action_sentence(event_creator, event_card_title, event) + "person" end end - - def comment_event_action_sentence(event_creator, event_card_title, event) - "#{event_creator} commented on #{event_card_title}" - end - - def card_event_action_sentence(event_creator, event_card_title, event) - case event.action - when "card_assigned" - if event.assignees.include?(Current.user) - "#{event_creator} will handle #{event_card_title}" - else - "#{event_creator} assigned #{h event.assignees.pluck(:name).to_sentence} to #{event_card_title}" - end - when "card_unassigned" - "#{event_creator} unassigned #{h(event.assignees.include?(Current.user) ? "yourself" : event.assignees.pluck(:name).to_sentence)} from #{event_card_title}" - when "card_published" - "#{event_creator} added #{event_card_title}" - when "card_closed" - "#{event_creator} moved #{event_card_title} to “Done”" - when "card_reopened" - "#{event_creator} reopened #{event_card_title}" - when "card_postponed" - "#{event_creator} moved #{event_card_title} to “Not Now”" - when "card_auto_postponed" - "#{event_card_title} was closed as “Not Now” due to inactivity" - when "card_resumed" - "#{event_creator} resumed #{event_card_title}" - when "card_title_changed" - "#{event_creator} renamed #{event_card_title} (was: “#{h event.particulars.dig('particulars', 'old_title')}”)" - when "card_board_changed", "card_collection_changed" - "#{event_creator} moved #{event_card_title} to “#{h (event.particulars.dig('particulars', 'new_board') || event.particulars.dig('particulars', 'new_collection'))}”" - when "card_triaged" - "#{event_creator} moved #{event_card_title} to “#{h event.particulars.dig('particulars', 'column')}”" - when "card_sent_back_to_triage" - "#{event_creator} moved #{event_card_title} back to “Maybe?”" - end - end - - def event_card_title(card) - tag.span card.title, class: "txt-underline" - end - - def event_due_date(event) - event.particulars.dig("particulars", "due_date").to_date.strftime("%B %-d") - end - - def event_action_icon(event) - case event.action - when "card_assigned" - "assigned" - when "card_unassigned" - "minus" - when "comment_created" - "comment" - when "card_title_changed" - "rename" - when "card_board_changed" - "move" - else - "person" - end - end end diff --git a/app/models/event.rb b/app/models/event.rb index ad77d3492..74a3bb20f 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -22,6 +22,10 @@ class Event < ApplicationRecord eventable end + def description_for(user) + Event::Description.new(self, user) + end + private def dispatch_webhooks Event::WebhookDispatchJob.perform_later(self) diff --git a/app/models/event/description.rb b/app/models/event/description.rb new file mode 100644 index 000000000..f743ce0e2 --- /dev/null +++ b/app/models/event/description.rb @@ -0,0 +1,108 @@ +class Event::Description + include ActionView::Helpers::TagHelper + include ERB::Util + + attr_reader :event, :user + + def initialize(event, user) + @event = event + @user = user + end + + def to_html + sentence(creator_tag, card_title_tag).html_safe + end + + def to_plain_text + sentence(creator_text, card.title) + end + + private + def card + @card ||= event.action.comment_created? ? event.eventable.card : event.eventable + end + + def creator_tag + tag.span data: { creator_id: event.creator.id } do + tag.span("You", data: { only_visible_to_you: true }) + + tag.span(h(event.creator.name), data: { only_visible_to_others: true }) + end + end + + def creator_text + event.creator == user ? "You" : h(event.creator.name) + end + + def card_title_tag + tag.span card.title, class: "txt-underline" + end + + def sentence(creator, card_title) + if event.action.comment_created? + comment_sentence(creator, card_title) + else + action_sentence(creator, card_title) + end + end + + def comment_sentence(creator, card_title) + "#{creator} commented on #{card_title}" + end + + def action_sentence(creator, card_title) + case event.action + when "card_assigned" + assigned_sentence(creator, card_title) + when "card_unassigned" + unassigned_sentence(creator, card_title) + when "card_published" + "#{creator} added #{card_title}" + when "card_closed" + %(#{creator} moved #{card_title} to "Done") + when "card_reopened" + "#{creator} reopened #{card_title}" + when "card_postponed" + %(#{creator} moved #{card_title} to "Not Now") + when "card_auto_postponed" + %(#{card_title} was closed as "Not Now" due to inactivity) + when "card_resumed" + "#{creator} resumed #{card_title}" + when "card_title_changed" + renamed_sentence(creator, card_title) + when "card_board_changed", "card_collection_changed" + moved_sentence(creator, card_title) + when "card_triaged" + triaged_sentence(creator, card_title) + when "card_sent_back_to_triage" + %(#{creator} moved #{card_title} back to "Maybe?") + end + end + + def assigned_sentence(creator, card_title) + if event.assignees.include?(user) + "#{creator} will handle #{card_title}" + else + "#{creator} assigned #{h event.assignees.pluck(:name).to_sentence} to #{card_title}" + end + end + + def unassigned_sentence(creator, card_title) + assignees_text = event.assignees.include?(user) ? "yourself" : event.assignees.pluck(:name).to_sentence + "#{creator} unassigned #{h(assignees_text)} from #{card_title}" + end + + def renamed_sentence(creator, card_title) + old_title = event.particulars.dig("particulars", "old_title") + %(#{creator} renamed #{card_title} (was: "#{h old_title}")) + end + + def moved_sentence(creator, card_title) + new_location = event.particulars.dig("particulars", "new_board") || event.particulars.dig("particulars", "new_collection") + %(#{creator} moved #{card_title} to "#{h new_location}") + end + + def triaged_sentence(creator, card_title) + column = event.particulars.dig("particulars", "column") + %(#{creator} moved #{card_title} to "#{h column}") + end +end diff --git a/app/views/events/event/_layout.html.erb b/app/views/events/event/_layout.html.erb index 1cd1e6831..0cd0fee07 100644 --- a/app/views/events/event/_layout.html.erb +++ b/app/views/events/event/_layout.html.erb @@ -32,7 +32,7 @@
      - <%= event_action_sentence_tag(event) %> + <%= event.description_for(Current.user).to_html %> <%= yield %> diff --git a/app/views/webhooks/event.html.erb b/app/views/webhooks/event.html.erb index a9835c21e..f2920e8d0 100644 --- a/app/views/webhooks/event.html.erb +++ b/app/views/webhooks/event.html.erb @@ -1,4 +1,4 @@ -<%= event_action_sentence_text(@event) %> +<%= @event.description_for(Current.user).to_plain_text %> <% if @event.eventable %> <%= link_to "↗︎", polymorphic_url(@event.eventable) %> <% end %> From 7a23e0bdd5f12a14073a38774c62c7978468f6af Mon Sep 17 00:00:00 2001 From: Kevin McConnell Date: Thu, 6 Nov 2025 09:56:28 +0000 Subject: [PATCH 021/175] Serve default error pages --- config/application.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/config/application.rb b/config/application.rb index b1a7da2be..7f52b3117 100644 --- a/config/application.rb +++ b/config/application.rb @@ -27,9 +27,6 @@ module Fizzy # enable load_async config.active_record.async_query_executor = :global_thread_pool - # use routes for error pages instead of static files - config.exceptions_app = self.routes - # include the tenant in query logs config.active_record.query_log_tags_enabled = true config.active_record.query_log_tags = [ :tenant ] From f5dfcd461c538bf66838e7bc63180e28808b3261 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 6 Nov 2025 11:01:21 +0100 Subject: [PATCH 022/175] Rename method --- app/models/event/description.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/event/description.rb b/app/models/event/description.rb index f743ce0e2..d474b5e73 100644 --- a/app/models/event/description.rb +++ b/app/models/event/description.rb @@ -10,11 +10,11 @@ class Event::Description end def to_html - sentence(creator_tag, card_title_tag).html_safe + to_sentence(creator_tag, card_title_tag).html_safe end def to_plain_text - sentence(creator_text, card.title) + to_sentence(creator_text, card.title) end private @@ -37,7 +37,7 @@ class Event::Description tag.span card.title, class: "txt-underline" end - def sentence(creator, card_title) + def to_sentence(creator, card_title) if event.action.comment_created? comment_sentence(creator, card_title) else From e4cb5579f05c6b4b55d9a0fa30894ae139be37c4 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 6 Nov 2025 11:02:20 +0100 Subject: [PATCH 023/175] Order methods --- app/models/event/description.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/models/event/description.rb b/app/models/event/description.rb index d474b5e73..ab0305d96 100644 --- a/app/models/event/description.rb +++ b/app/models/event/description.rb @@ -18,8 +18,12 @@ class Event::Description end private - def card - @card ||= event.action.comment_created? ? event.eventable.card : event.eventable + def to_sentence(creator, card_title) + if event.action.comment_created? + comment_sentence(creator, card_title) + else + action_sentence(creator, card_title) + end end def creator_tag @@ -29,20 +33,16 @@ class Event::Description end end - def creator_text - event.creator == user ? "You" : h(event.creator.name) - end - def card_title_tag tag.span card.title, class: "txt-underline" end - def to_sentence(creator, card_title) - if event.action.comment_created? - comment_sentence(creator, card_title) - else - action_sentence(creator, card_title) - end + def creator_text + event.creator == user ? "You" : h(event.creator.name) + end + + def card + @card ||= event.action.comment_created? ? event.eventable.card : event.eventable end def comment_sentence(creator, card_title) From 82adcecfb15ac998defb19283b03bc5b898b04c1 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 6 Nov 2025 11:05:42 +0100 Subject: [PATCH 024/175] Add some sanity tests for event descriptions --- test/models/event/description_test.rb | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/models/event/description_test.rb diff --git a/test/models/event/description_test.rb b/test/models/event/description_test.rb new file mode 100644 index 000000000..6fa8d20d2 --- /dev/null +++ b/test/models/event/description_test.rb @@ -0,0 +1,35 @@ +require "test_helper" + +class Event::DescriptionTest < ActiveSupport::TestCase + test "generates html description for card published event" do + description = events(:logo_published).description_for(users(:david)) + + assert_includes description.to_html, "added" + assert_includes description.to_html, "logo" + end + + test "generates plain text description for card published event" do + description = events(:logo_published).description_for(users(:david)) + + assert_includes description.to_plain_text, "You added" + assert_includes description.to_plain_text, "logo" + end + + test "generates description for comment event" do + description = events(:layout_commented).description_for(users(:jz)) + + assert_includes description.to_plain_text, "David commented on" + end + + test "uses 'You' when event creator is the current user" do + description = events(:logo_published).description_for(users(:david)) + + assert_includes description.to_plain_text, "You added" + end + + test "uses creator name when event creator is not the current user" do + description = events(:logo_published).description_for(users(:jz)) + + assert_includes description.to_plain_text, "David added" + end +end From 9291e64deb2faefa80bbd60473fa8b7cfb9cef0a Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 6 Nov 2025 11:21:22 +0100 Subject: [PATCH 025/175] Add test for weebhook delivery test --- test/models/webhook/delivery_test.rb | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/models/webhook/delivery_test.rb b/test/models/webhook/delivery_test.rb index 244a9a2cc..8deab09ae 100644 --- a/test/models/webhook/delivery_test.rb +++ b/test/models/webhook/delivery_test.rb @@ -218,4 +218,44 @@ class Webhook::DeliveryTest < ActiveSupport::TestCase assert Webhook::Delivery.exists?(fresh_delivery.id) assert_not Webhook::Delivery.exists?(stale_delivery.id) end + + test "renders 'You' when event creator is current user" do + webhook = Webhook.create!( + board: boards(:writebook), + name: "Basecamp", + url: "https://3.basecamp.com/123/integrations/webhook/buckets/456/chats/789/lines" + ) + event = events(:logo_published) + delivery = Webhook::Delivery.create!(webhook: webhook, event: event) + + Current.session = sessions(:david) + + request_stub = stub_request(:post, webhook.url) + .with { |request| CGI.parse(request.body)["content"].first.include?("You added") } + .to_return(status: 200) + + delivery.deliver + + assert_requested request_stub + end + + test "renders creator name when event creator is not current user" do + webhook = Webhook.create!( + board: boards(:writebook), + name: "Basecamp", + url: "https://3.basecamp.com/123/integrations/webhook/buckets/456/chats/789/lines" + ) + event = events(:logo_published) + delivery = Webhook::Delivery.create!(webhook: webhook, event: event) + + Current.session = sessions(:kevin) + + request_stub = stub_request(:post, webhook.url) + .with { |request| CGI.parse(request.body)["content"].first.include?("David added") } + .to_return(status: 200) + + delivery.deliver + + assert_requested request_stub + end end From 0c48e5f30a962885623f32f0a73b9f7b61c0f074 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 6 Nov 2025 11:43:22 +0100 Subject: [PATCH 026/175] No need to escape here --- app/models/event/description.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/event/description.rb b/app/models/event/description.rb index ab0305d96..3c6d2200d 100644 --- a/app/models/event/description.rb +++ b/app/models/event/description.rb @@ -29,7 +29,7 @@ class Event::Description def creator_tag tag.span data: { creator_id: event.creator.id } do tag.span("You", data: { only_visible_to_you: true }) + - tag.span(h(event.creator.name), data: { only_visible_to_others: true }) + tag.span(event.creator.name, data: { only_visible_to_others: true }) end end From 6fcb51703d5790eb3313601148ffdfbcb4657c3d Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 6 Nov 2025 11:44:03 +0100 Subject: [PATCH 027/175] Format --- app/models/event/description.rb | 48 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/app/models/event/description.rb b/app/models/event/description.rb index 3c6d2200d..87a5ec6d0 100644 --- a/app/models/event/description.rb +++ b/app/models/event/description.rb @@ -51,30 +51,30 @@ class Event::Description def action_sentence(creator, card_title) case event.action - when "card_assigned" - assigned_sentence(creator, card_title) - when "card_unassigned" - unassigned_sentence(creator, card_title) - when "card_published" - "#{creator} added #{card_title}" - when "card_closed" - %(#{creator} moved #{card_title} to "Done") - when "card_reopened" - "#{creator} reopened #{card_title}" - when "card_postponed" - %(#{creator} moved #{card_title} to "Not Now") - when "card_auto_postponed" - %(#{card_title} was closed as "Not Now" due to inactivity) - when "card_resumed" - "#{creator} resumed #{card_title}" - when "card_title_changed" - renamed_sentence(creator, card_title) - when "card_board_changed", "card_collection_changed" - moved_sentence(creator, card_title) - when "card_triaged" - triaged_sentence(creator, card_title) - when "card_sent_back_to_triage" - %(#{creator} moved #{card_title} back to "Maybe?") + when "card_assigned" + assigned_sentence(creator, card_title) + when "card_unassigned" + unassigned_sentence(creator, card_title) + when "card_published" + "#{creator} added #{card_title}" + when "card_closed" + %(#{creator} moved #{card_title} to "Done") + when "card_reopened" + "#{creator} reopened #{card_title}" + when "card_postponed" + %(#{creator} moved #{card_title} to "Not Now") + when "card_auto_postponed" + %(#{card_title} was closed as "Not Now" due to inactivity) + when "card_resumed" + "#{creator} resumed #{card_title}" + when "card_title_changed" + renamed_sentence(creator, card_title) + when "card_board_changed", "card_collection_changed" + moved_sentence(creator, card_title) + when "card_triaged" + triaged_sentence(creator, card_title) + when "card_sent_back_to_triage" + %(#{creator} moved #{card_title} back to "Maybe?") end end From 676c2a42fe1eb93eb60680a775097dbd25ce3dd4 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 6 Nov 2025 14:16:01 +0100 Subject: [PATCH 028/175] Always use the creator name for the plain text form You does not make sense for webhooks --- app/models/event/description.rb | 6 +++--- test/models/event/description_test.rb | 6 +++--- test/models/webhook/delivery_test.rb | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/event/description.rb b/app/models/event/description.rb index 87a5ec6d0..7718a795c 100644 --- a/app/models/event/description.rb +++ b/app/models/event/description.rb @@ -14,7 +14,7 @@ class Event::Description end def to_plain_text - to_sentence(creator_text, card.title) + to_sentence(creator_name, card.title) end private @@ -37,8 +37,8 @@ class Event::Description tag.span card.title, class: "txt-underline" end - def creator_text - event.creator == user ? "You" : h(event.creator.name) + def creator_name + h(event.creator.name) end def card diff --git a/test/models/event/description_test.rb b/test/models/event/description_test.rb index 6fa8d20d2..e5bd583a7 100644 --- a/test/models/event/description_test.rb +++ b/test/models/event/description_test.rb @@ -11,7 +11,7 @@ class Event::DescriptionTest < ActiveSupport::TestCase test "generates plain text description for card published event" do description = events(:logo_published).description_for(users(:david)) - assert_includes description.to_plain_text, "You added" + assert_includes description.to_plain_text, "David added" assert_includes description.to_plain_text, "logo" end @@ -21,10 +21,10 @@ class Event::DescriptionTest < ActiveSupport::TestCase assert_includes description.to_plain_text, "David commented on" end - test "uses 'You' when event creator is the current user" do + test "uses always the name even when the event creator is the current user" do description = events(:logo_published).description_for(users(:david)) - assert_includes description.to_plain_text, "You added" + assert_includes description.to_plain_text, "David added" end test "uses creator name when event creator is not the current user" do diff --git a/test/models/webhook/delivery_test.rb b/test/models/webhook/delivery_test.rb index 8deab09ae..e803526ac 100644 --- a/test/models/webhook/delivery_test.rb +++ b/test/models/webhook/delivery_test.rb @@ -219,7 +219,7 @@ class Webhook::DeliveryTest < ActiveSupport::TestCase assert_not Webhook::Delivery.exists?(stale_delivery.id) end - test "renders 'You' when event creator is current user" do + test "renders the creator name when event creator is current user" do webhook = Webhook.create!( board: boards(:writebook), name: "Basecamp", @@ -231,7 +231,7 @@ class Webhook::DeliveryTest < ActiveSupport::TestCase Current.session = sessions(:david) request_stub = stub_request(:post, webhook.url) - .with { |request| CGI.parse(request.body)["content"].first.include?("You added") } + .with { |request| CGI.parse(request.body)["content"].first.include?("David added") } .to_return(status: 200) delivery.deliver From c31354d6c64ed95dcd01330145fae359edf4f340 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 6 Nov 2025 14:31:19 +0100 Subject: [PATCH 029/175] Fix: show columns in the right order https://app.fizzy.do/5986089/cards/2820 --- app/controllers/cards/columns_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/cards/columns_controller.rb b/app/controllers/cards/columns_controller.rb index f250f2dd6..3e32b1cb8 100644 --- a/app/controllers/cards/columns_controller.rb +++ b/app/controllers/cards/columns_controller.rb @@ -1,7 +1,7 @@ class Cards::ColumnsController < ApplicationController def edit @card = Current.user.accessible_cards.find(params[:card_id]) - @columns = @card.board.columns + @columns = @card.board.columns.sorted fresh_when etag: [ @card, @columns ] end From 448a01ae3a20c8d0767cd4e4487c47840311099f Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 6 Nov 2025 16:02:07 +0100 Subject: [PATCH 030/175] Update Lexxy to fix android issues See https://github.com/basecamp/lexxy/pull/435 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 78c3baabc..1a8e39cdb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -290,7 +290,7 @@ GEM logger (~> 1.6) letter_opener (1.10.0) launchy (>= 2.2, < 4) - lexxy (0.1.18.beta) + lexxy (0.1.19.beta) rails (>= 8.0.2) lint_roller (1.1.0) logger (1.7.0) From e9c8522929016051d5dd203146e61c792b6387ad Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 6 Nov 2025 11:00:09 -0600 Subject: [PATCH 031/175] Use different icons for column buttons --- app/assets/images/collapse.svg | 1 + app/assets/images/maximize.svg | 1 + app/assets/stylesheets/card-columns.css | 115 +++++++++--------- app/assets/stylesheets/icons.css | 2 + app/views/boards/show/_closed.html.erb | 5 +- app/views/boards/show/_column.html.erb | 11 +- app/views/boards/show/_expander.html.erb | 5 +- app/views/boards/show/_not_now.html.erb | 5 +- app/views/boards/show/_stream.html.erb | 10 +- app/views/boards/show/menu/_column.html.erb | 2 +- app/views/boards/show/menu/_columns.html.erb | 2 +- .../{_expand.html.erb => _maximize.html.erb} | 4 +- app/views/public/boards/show/_closed.html.erb | 10 +- app/views/public/boards/show/_column.html.erb | 9 +- .../public/boards/show/_not_now.html.erb | 10 +- app/views/public/boards/show/_stream.html.erb | 18 +-- 16 files changed, 115 insertions(+), 95 deletions(-) create mode 100644 app/assets/images/collapse.svg create mode 100644 app/assets/images/maximize.svg rename app/views/boards/show/menu/{_expand.html.erb => _maximize.html.erb} (53%) diff --git a/app/assets/images/collapse.svg b/app/assets/images/collapse.svg new file mode 100644 index 000000000..90effb7cf --- /dev/null +++ b/app/assets/images/collapse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/maximize.svg b/app/assets/images/maximize.svg new file mode 100644 index 000000000..425fa32d0 --- /dev/null +++ b/app/assets/images/maximize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 7800826bf..ba2d51e05 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -147,12 +147,6 @@ } } - .cards__header { - .cards.is-collapsed & { - block-size: 100%; - } - } - /* The wrapper around the cards used to clip overflow while transitioning. * Also, don't resize cards while transitioning to avoid reflow. */ .cards__list { @@ -225,6 +219,46 @@ /* Column Elements /* ------------------------------------------------------------------------ */ + .cards__header { + .cards.is-collapsed & { + block-size: 100%; + } + + .cards:not(.is-collapsed) & { + display: grid; + grid-template-areas: "menu expander maximize"; + grid-template-columns: auto 1fr auto; + margin-block-end: calc(0.5 * var(--cards-gap)); + } + } + + .cards__menu .btn--circle, + .cards__maximize-button { + --btn-background: transparent; + + block-size: var(--column-width-collapsed); + inline-size: var(--column-width-collapsed); + opacity: 0; + outline-offset: -2px; + + .cards:hover &, + &:focus-visible { + opacity: 1; + } + + .cards.is-collapsed & { + display: none; + } + } + + .cards__menu { + z-index: var(--z-popup); + } + + .cards__maximize-button { + grid-area: maximize; + } + .cards__expander { align-items: center; border-radius: 99rem; @@ -234,6 +268,8 @@ font-size: var(--text-x-small); font-weight: 600; gap: 0.5ch; + grid-area: expander; + outline-offset: -2px; position: relative; text-transform: uppercase; @@ -293,7 +329,6 @@ .cards:not(.is-collapsed) & { inline-size: 100%; - margin-block-end: calc(0.5 * var(--cards-gap)); justify-content: center; } @@ -301,39 +336,6 @@ cursor: unset; filter: none; } - - @media (any-hover: hover) { - .btn--circle, - ~ .btn--circle, - ~ .cards__expander-menu .btn--circle:not(label) { - transition: opacity 300ms ease-in-out; - opacity: 0; - - &:focus-visible, - .cards:hover & { - opacity: 1; - } - } - } - } - - .btn.cards__expander-button { - --btn-background: transparent; - - block-size: var(--column-width-collapsed); - inline-size: var(--column-width-collapsed); - inset: 0 0 auto auto; - position: absolute; - translate: calc(var(--column-gap) * -2); - z-index: var(--z-popup); - - .cards--considering & { - translate: none; - } - - .cards.is-collapsed & { - display: none; - } } .cards__expander-count { @@ -359,25 +361,28 @@ } .cards:not(.is-collapsed, .cards--considering) & { + align-items: center; + display: flex; + gap: 0.25ch; max-inline-size: calc(100% - var(--column-width-collapsed) * 2); } - } - .cards__expander-menu { - inset: 0 auto auto 0; - position: absolute; - translate: calc(var(--column-gap) * 2); - z-index: var(--z-popup); + .icon--collapse { + --icon-size: 1.15em; - .btn--circle { - --btn-background: transparent; + opacity: 0; + scale: 1.5; + transition: 150ms ease-out; + transition-property: opacity, scale; - block-size: var(--column-width-collapsed); - inline-size: var(--column-width-collapsed); - } + .cards.is-collapsed & { + display: none; + } - .cards.is-collapsed & { - display: none; + .cards:not(.is-collapsed) .cards__expander:hover & { + opacity: 0.66; + scale: 1; + } } } @@ -504,8 +509,8 @@ @media (any-hover: hover) { .card:hover & { - opacity: 1; - } + opacity: 1; + } } } } diff --git a/app/assets/stylesheets/icons.css b/app/assets/stylesheets/icons.css index 8575b72f2..c463fbd7d 100644 --- a/app/assets/stylesheets/icons.css +++ b/app/assets/stylesheets/icons.css @@ -42,6 +42,7 @@ .icon--clipboard { --svg: url("clipboard.svg "); } .icon--close { --svg: url("close.svg "); } .icon--close-circle { --svg: url("close-circle.svg "); } + .icon--collapse { --svg: url("collapse.svg "); } .icon--board { --svg: url("board.svg "); } .icon--board-add { --svg: url("board-add.svg "); } .icon--column-left { --svg: url("column-left.svg "); } @@ -64,6 +65,7 @@ .icon--lock { --svg: url("lock.svg "); } .icon--logout { --svg: url("logout.svg "); } .icon--marker { --svg: url("marker.svg "); } + .icon--maximize { --svg: url("maximize.svg "); } .icon--menu { --svg: url("menu.svg "); } .icon--menu-dots-horizontal { --svg: url("menu-dots-horizontal.svg "); } .icon--menu-dots-vertical { --svg: url("menu-dots-vertical.svg "); } diff --git a/app/views/boards/show/_closed.html.erb b/app/views/boards/show/_closed.html.erb index 180832660..51c587c78 100644 --- a/app/views/boards/show/_closed.html.erb +++ b/app/views/boards/show/_closed.html.erb @@ -8,9 +8,8 @@ >
      - <%= render "boards/show/expander", title: "Done", count: board.cards.closed.count, column_id: "closed-cards" do %> - <%= render "boards/show/menu/expand", column_path: board_columns_closed_path(board) %> - <% end %> + <%= render "boards/show/expander", title: "Done", count: board.cards.closed.count, column_id: "closed-cards" %> + <%= render "boards/show/menu/maximize", column_path: board_columns_closed_path(board) %>
      <%= column_frame_tag :closed_column, src: board_columns_closed_path(board) %>
      diff --git a/app/views/boards/show/_column.html.erb b/app/views/boards/show/_column.html.erb index 27c097481..bbbc4b11d 100644 --- a/app/views/boards/show/_column.html.erb +++ b/app/views/boards/show/_column.html.erb @@ -10,13 +10,12 @@ >
      - <%= render "boards/show/expander", title: column.name, count: column.cards.active.count, column_id: dom_id(column) do %> - <%= render "boards/show/menu/column", column: column %> - <% end %> + <%= render "boards/show/menu/column", column: column %> + <%= render "boards/show/expander", title: column.name, count: column.cards.active.count, column_id: dom_id(column) %> - <%= link_to board_column_path(column.board, column), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> - <%= icon_tag "expand", class: "translucent" %> - Expand column + <%= link_to board_column_path(column.board, column), class: "cards__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> + <%= icon_tag "maximize", class: "translucent" %> + Maximize column <% end %>
      <%= column_frame_tag dom_id(column, :cards), src: board_column_path(column.board, column) %> diff --git a/app/views/boards/show/_expander.html.erb b/app/views/boards/show/_expander.html.erb index ea58f061f..d01debadf 100644 --- a/app/views/boards/show/_expander.html.erb +++ b/app/views/boards/show/_expander.html.erb @@ -1,6 +1,9 @@ <%= yield if block_given? %> diff --git a/app/views/boards/show/_not_now.html.erb b/app/views/boards/show/_not_now.html.erb index af8741431..414b608c9 100644 --- a/app/views/boards/show/_not_now.html.erb +++ b/app/views/boards/show/_not_now.html.erb @@ -8,9 +8,8 @@ >
      - <%= render "boards/show/expander", title: "Not Now", count: board.cards.postponed.count, column_id: "not-now" do %> - <%= render "boards/show/menu/expand", column_path: board_columns_not_now_path(board) %> - <% end %> + <%= render "boards/show/expander", title: "Not Now", count: board.cards.postponed.count, column_id: "not-now" %> + <%= render "boards/show/menu/maximize", column_path: board_columns_not_now_path(board) %>
      <%= column_frame_tag :not_now_column, src: board_columns_not_now_path(board) %>
      diff --git a/app/views/boards/show/_stream.html.erb b/app/views/boards/show/_stream.html.erb index 5d5f6508f..843f61192 100644 --- a/app/views/boards/show/_stream.html.erb +++ b/app/views/boards/show/_stream.html.erb @@ -6,12 +6,12 @@
      -

      Maybe?

      - <%= link_to board_columns_stream_path(board), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> - <%= icon_tag "expand", class: "translucent" %> - Expand column - <% end %> +

      Maybe?

      + <%= link_to board_columns_stream_path(board), class: "cards__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> + <%= icon_tag "maximize", class: "translucent" %> + Expand column + <% end %>
      <%= render "columns/show/add_card_button", board: board %> <% if page.used? %> diff --git a/app/views/boards/show/menu/_column.html.erb b/app/views/boards/show/menu/_column.html.erb index 52637cce4..fa3ab38ff 100644 --- a/app/views/boards/show/menu/_column.html.erb +++ b/app/views/boards/show/menu/_column.html.erb @@ -1,4 +1,4 @@ -
      +
      diff --git a/app/views/boards/show/menu/_expand.html.erb b/app/views/boards/show/menu/_maximize.html.erb similarity index 53% rename from app/views/boards/show/menu/_expand.html.erb rename to app/views/boards/show/menu/_maximize.html.erb index 4413739d1..a0f3b8a7c 100644 --- a/app/views/boards/show/menu/_expand.html.erb +++ b/app/views/boards/show/menu/_maximize.html.erb @@ -1,4 +1,4 @@ -<%= link_to column_path, class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> - <%= icon_tag "expand", class: "translucent" %> +<%= link_to column_path, class: "cards__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> + <%= icon_tag "maximize", class: "translucent" %> Expand column <% end %> diff --git a/app/views/public/boards/show/_closed.html.erb b/app/views/public/boards/show/_closed.html.erb index 717f81324..b072ba7bb 100644 --- a/app/views/public/boards/show/_closed.html.erb +++ b/app/views/public/boards/show/_closed.html.erb @@ -2,8 +2,10 @@ data-collapsible-columns-target="column" data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" > - - <%= render "boards/show/expander", title: "Done", count: board.cards.closed.count, column_id: "closed-cards" %> - - <%= column_frame_tag :closed_column, src: public_board_columns_closed_path(board.publication.key) %> +
      +
      + <%= render "boards/show/expander", title: "Done", count: board.cards.closed.count, column_id: "closed-cards" %> +
      + <%= column_frame_tag :closed_column, src: public_board_columns_closed_path(board.publication.key) %> +
diff --git a/app/views/public/boards/show/_column.html.erb b/app/views/public/boards/show/_column.html.erb index 99e59de0d..2010d6f47 100644 --- a/app/views/public/boards/show/_column.html.erb +++ b/app/views/public/boards/show/_column.html.erb @@ -4,7 +4,10 @@ data-controller="clicker" data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" > - <%= render "boards/show/expander", title: column.name, count: column.cards.active.count, column_id: dom_id(column) %> - - <%= column_frame_tag dom_id(column, :cards), src: public_board_column_path(column.board.publication.key, column) %> +
+
+ <%= render "boards/show/expander", title: column.name, count: column.cards.active.count, column_id: dom_id(column) %> +
+ <%= column_frame_tag dom_id(column, :cards), src: public_board_column_path(column.board.publication.key, column) %> +
diff --git a/app/views/public/boards/show/_not_now.html.erb b/app/views/public/boards/show/_not_now.html.erb index dad3ca0e4..906fd444d 100644 --- a/app/views/public/boards/show/_not_now.html.erb +++ b/app/views/public/boards/show/_not_now.html.erb @@ -2,8 +2,10 @@ data-collapsible-columns-target="column" data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" > - - <%= render "boards/show/expander", title: "Not Now", count: board.cards.postponed.count, column_id: "not-now" %> - - <%= column_frame_tag :not_now_column, src: public_board_columns_not_now_path(board.publication.key) %> +
+
+ <%= render "boards/show/expander", title: "Not Now", count: board.cards.postponed.count, column_id: "not-now" %> +
+ <%= column_frame_tag :not_now_column, src: public_board_columns_not_now_path(board.publication.key) %> +
diff --git a/app/views/public/boards/show/_stream.html.erb b/app/views/public/boards/show/_stream.html.erb index 79a029184..1a902c087 100644 --- a/app/views/public/boards/show/_stream.html.erb +++ b/app/views/public/boards/show/_stream.html.erb @@ -1,12 +1,16 @@
-
-

Maybe?

-
+
+
+
+

Maybe?

+
+
- <% if page.used? %> - <%= with_automatic_pagination "the-stream", @page do %> - <%= render "public/boards/columns/list", cards: page.records %> + <% if page.used? %> + <%= with_automatic_pagination "the-stream", @page do %> + <%= render "public/boards/columns/list", cards: page.records %> + <% end %> <% end %> - <% end %> +
From 1c0794cd51365dc96c8ed53c92b47fd56866fb33 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 6 Nov 2025 11:57:52 -0600 Subject: [PATCH 032/175] Signup page a11y tweaks --- app/assets/stylesheets/_global.css | 2 +- app/assets/stylesheets/utilities.css | 2 +- app/views/layouts/public.html.erb | 6 +++--- app/views/layouts/shared/_head.html.erb | 3 +-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/_global.css b/app/assets/stylesheets/_global.css index c18cf4090..9f1092f82 100644 --- a/app/assets/stylesheets/_global.css +++ b/app/assets/stylesheets/_global.css @@ -89,7 +89,7 @@ --lch-ink-darkest: 26% 0.05 264; --lch-ink-darker: 40% 0.026 262; - --lch-ink-dark: 57% 0.014 260; + --lch-ink-dark: 56% 0.014 260; --lch-ink-medium: 66% 0.008 258; --lch-ink-light: 84% 0.005 256; --lch-ink-lighter: 92% 0.003 254; diff --git a/app/assets/stylesheets/utilities.css b/app/assets/stylesheets/utilities.css index 99bac2252..6ad6b2089 100644 --- a/app/assets/stylesheets/utilities.css +++ b/app/assets/stylesheets/utilities.css @@ -16,7 +16,7 @@ .txt-ink { color: var(--color-ink); } .txt-reversed { color: var(--color-ink-inverted); } .txt-negative { color: var(--color-negative); } - .txt-subtle { color: var(--color-ink-medium); } + .txt-subtle { color: var(--color-ink-dark); } .txt-alert { color: var(--color-marker); } .txt-undecorated { text-decoration: none; } .txt-underline { text-decoration: underline; } diff --git a/app/views/layouts/public.html.erb b/app/views/layouts/public.html.erb index c217f44bb..98c42de94 100644 --- a/app/views/layouts/public.html.erb +++ b/app/views/layouts/public.html.erb @@ -1,13 +1,13 @@ - + <%= render "layouts/shared/head" %>
From f8421eca89bd31691bf916c40a3f36a41a37bd9d Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Thu, 6 Nov 2025 15:23:17 -0600 Subject: [PATCH 039/175] Match style of back-to-board buttons inside the app --- app/views/public/cards/show.html.erb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/views/public/cards/show.html.erb b/app/views/public/cards/show.html.erb index b1201b1f3..2d12431a0 100644 --- a/app/views/public/cards/show.html.erb +++ b/app/views/public/cards/show.html.erb @@ -13,12 +13,14 @@ <% end %> <% content_for :header do %> - <%= link_to published_board_url(@card.board), class: "header__title btn borderless txt-large", style: "--btn-padding: 0.25ch 1ch 0.25ch 0.75ch; view-transistion-name: card-board-title;", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %> - - ← - <%= @card.board.name %> - - <% end %> +
+ <%= link_to published_board_url(@card.board), class: "btn borderless txt-medium", style: "view-transistion-name: card-board-title;", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %> + + ← + <%= @card.board.name %> + + <% end %> +
<% end %>
Date: Thu, 6 Nov 2025 15:36:00 -0600 Subject: [PATCH 040/175] Fix the column mark-up had drifted This ensure the transitions work properly and that the columns all align nicely at the top of the board --- app/views/public/boards/show/_closed.html.erb | 8 ++++++-- app/views/public/boards/show/_column.html.erb | 8 ++++++-- app/views/public/boards/show/_not_now.html.erb | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/views/public/boards/show/_closed.html.erb b/app/views/public/boards/show/_closed.html.erb index 717f81324..e83f2eae0 100644 --- a/app/views/public/boards/show/_closed.html.erb +++ b/app/views/public/boards/show/_closed.html.erb @@ -3,7 +3,11 @@ data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" > - <%= render "boards/show/expander", title: "Done", count: board.cards.closed.count, column_id: "closed-cards" %> +
+
+ <%= render "boards/show/expander", title: "Done", count: board.cards.closed.count, column_id: "closed-cards" %> +
- <%= column_frame_tag :closed_column, src: public_board_columns_closed_path(board.publication.key) %> + <%= column_frame_tag :closed_column, src: public_board_columns_closed_path(board.publication.key) %> +
diff --git a/app/views/public/boards/show/_column.html.erb b/app/views/public/boards/show/_column.html.erb index 99e59de0d..ae6b515fd 100644 --- a/app/views/public/boards/show/_column.html.erb +++ b/app/views/public/boards/show/_column.html.erb @@ -4,7 +4,11 @@ data-controller="clicker" data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" > - <%= render "boards/show/expander", title: column.name, count: column.cards.active.count, column_id: dom_id(column) %> +
+
+ <%= render "boards/show/expander", title: column.name, count: column.cards.active.count, column_id: dom_id(column) %> +
- <%= column_frame_tag dom_id(column, :cards), src: public_board_column_path(column.board.publication.key, column) %> + <%= column_frame_tag dom_id(column, :cards), src: public_board_column_path(column.board.publication.key, column) %> +
diff --git a/app/views/public/boards/show/_not_now.html.erb b/app/views/public/boards/show/_not_now.html.erb index dad3ca0e4..f108596b2 100644 --- a/app/views/public/boards/show/_not_now.html.erb +++ b/app/views/public/boards/show/_not_now.html.erb @@ -3,7 +3,11 @@ data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle" > - <%= render "boards/show/expander", title: "Not Now", count: board.cards.postponed.count, column_id: "not-now" %> +
+
+ <%= render "boards/show/expander", title: "Not Now", count: board.cards.postponed.count, column_id: "not-now" %> +
- <%= column_frame_tag :not_now_column, src: public_board_columns_not_now_path(board.publication.key) %> + <%= column_frame_tag :not_now_column, src: public_board_columns_not_now_path(board.publication.key) %> +
From 7dd56c71c45c26ef035b792a1699ebc78d873277 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Thu, 6 Nov 2025 15:38:28 -0600 Subject: [PATCH 041/175] Match style with private boards --- app/views/public/boards/show.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/public/boards/show.html.erb b/app/views/public/boards/show.html.erb index ae5b75912..f5585f5e9 100644 --- a/app/views/public/boards/show.html.erb +++ b/app/views/public/boards/show.html.erb @@ -13,8 +13,8 @@ <% end %> <% content_for :header do %> -

- <%= @page_title %> +

+ <%= @page_title %>

<% end %> From bcc646e421ec9c94f2e1810304194c928d9dd6cd Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 6 Nov 2025 15:49:50 -0600 Subject: [PATCH 042/175] Nicer column x-scrolling and hover states --- app/assets/stylesheets/card-columns.css | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 7800826bf..e0d93c631 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -3,6 +3,10 @@ /* ------------------------------------------------------------------------ */ #main:has(.card-columns) { + --main-padding: 0; + } + + .card-columns { --bubble-size: 3.5rem; --cards-gap: min(1.2cqi, 1.7rem); --column-gap: 8px; @@ -14,10 +18,7 @@ --progress-max-cards: 20; --progress-max-height: 50dvh; - padding-inline: var(--column-gap); - } - - .card-columns { + -ms-overflow-style: none; /* Hide x-scrollbar on Edge */ container-type: inline-size; display: grid; gap: var(--column-gap); @@ -26,7 +27,14 @@ max-inline-size: var(--main-width); overflow-x: auto; overflow-y: hidden; + padding-block-end: var(--column-width-collapsed); position: relative; + scrollbar-width: none; /* Hide x-scrollbar on FF */ + + /* Hide x-scrollbar on Chrome/Safari/Opera */ + &::-webkit-scrollbar { + display: none; + } /* When it has something expanded */ &:has(.card-columns__left .cards:not(.is-collapsed), .card-columns__right .cards:not(.is-collapsed)) { @@ -51,10 +59,12 @@ .card-columns__left { justify-content: end; + padding-inline-start: var(--column-gap); } .card-columns__right { justify-content: start; + padding-inline-end: var(--column-gap); } /* Column From 081706e676c51147234cc6f6254e0194b73d97bd Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Thu, 6 Nov 2025 15:55:30 -0600 Subject: [PATCH 043/175] Normal users shouldn't be able to delete a board Unless they created it --- app/views/boards/edit.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/boards/edit.html.erb b/app/views/boards/edit.html.erb index 206bc5ed5..45edcfbc6 100644 --- a/app/views/boards/edit.html.erb +++ b/app/views/boards/edit.html.erb @@ -33,6 +33,6 @@
<%= render "boards/edit/auto_close", board: @board %> <%= render "boards/edit/publication", board: @board %> - <%= render "boards/edit/delete", board: @board %> + <%= render "boards/edit/delete", board: @board if Current.user.can_administer_card?(@board) %>
From 626bc1b9857656fe0694125ddd880264d0962500 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 6 Nov 2025 16:22:02 -0600 Subject: [PATCH 044/175] Remove redundant utility classes from quick filter --- app/views/filters/settings/_assignees.html.erb | 2 +- app/views/filters/settings/_boards.html.erb | 2 +- app/views/filters/settings/_closers.html.erb | 2 +- app/views/filters/settings/_creators.html.erb | 2 +- app/views/filters/settings/_indexed_by.html.erb | 2 +- app/views/filters/settings/_sorted_by.html.erb | 2 +- app/views/filters/settings/_time_window.html.erb | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/filters/settings/_assignees.html.erb b/app/views/filters/settings/_assignees.html.erb index 996116d63..0a60a4878 100644 --- a/app/views/filters/settings/_assignees.html.erb +++ b/app/views/filters/settings/_assignees.html.erb @@ -1,5 +1,5 @@ <% filter = user_filtering.filter %> -<%= tag.div class: "quick-filter position-relative", +<%= tag.div class: "quick-filter", data: { controller: "dialog multi-selection-combobox", action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside", diff --git a/app/views/filters/settings/_boards.html.erb b/app/views/filters/settings/_boards.html.erb index 5caf96d65..97a21618d 100644 --- a/app/views/filters/settings/_boards.html.erb +++ b/app/views/filters/settings/_boards.html.erb @@ -1,6 +1,6 @@ <% filter = user_filtering.filter %> -<%= tag.div class: "quick-filter position-relative", +<%= tag.div class: "quick-filter", data: { controller: "dialog multi-selection-combobox", action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside", diff --git a/app/views/filters/settings/_closers.html.erb b/app/views/filters/settings/_closers.html.erb index 5da789245..24e542b1e 100644 --- a/app/views/filters/settings/_closers.html.erb +++ b/app/views/filters/settings/_closers.html.erb @@ -1,6 +1,6 @@ <% filter = user_filtering.filter %> -<%= tag.div class: "quick-filter position-relative", +<%= tag.div class: "quick-filter", data: { controller: "dialog multi-selection-combobox", action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside", diff --git a/app/views/filters/settings/_creators.html.erb b/app/views/filters/settings/_creators.html.erb index 92385f7a2..8453fe745 100644 --- a/app/views/filters/settings/_creators.html.erb +++ b/app/views/filters/settings/_creators.html.erb @@ -1,6 +1,6 @@ <% filter = user_filtering.filter %> -<%= tag.div class: "quick-filter position-relative", +<%= tag.div class: "quick-filter", data: { controller: "dialog multi-selection-combobox", action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside", diff --git a/app/views/filters/settings/_indexed_by.html.erb b/app/views/filters/settings/_indexed_by.html.erb index 48bf476da..8175fea9e 100644 --- a/app/views/filters/settings/_indexed_by.html.erb +++ b/app/views/filters/settings/_indexed_by.html.erb @@ -1,6 +1,6 @@ <% filter = user_filtering.filter %> -<%= tag.div class: class_names("quick-filter position-relative"), +<%= tag.div class: class_names("quick-filter"), data: { controller: "dialog combobox", action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside", filter_show: user_filtering.show_indexed_by?, diff --git a/app/views/filters/settings/_sorted_by.html.erb b/app/views/filters/settings/_sorted_by.html.erb index 5379cfd17..e5c0adc4e 100644 --- a/app/views/filters/settings/_sorted_by.html.erb +++ b/app/views/filters/settings/_sorted_by.html.erb @@ -1,6 +1,6 @@ <% filter = user_filtering.filter %> -<%= tag.div class: class_names("quick-filter position-relative"), +<%= tag.div class: class_names("quick-filter"), data: { controller: "dialog combobox", action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside", diff --git a/app/views/filters/settings/_time_window.html.erb b/app/views/filters/settings/_time_window.html.erb index cfc827ecc..b747384ac 100644 --- a/app/views/filters/settings/_time_window.html.erb +++ b/app/views/filters/settings/_time_window.html.erb @@ -1,5 +1,5 @@ <% filter = user_filtering.filter %> -<%= tag.div class: "quick-filter position-relative", +<%= tag.div class: "quick-filter", data: { controller: "dialog", action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside", filter_show: filter.public_send("#{name}_window").present? } do %> <% end %> diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb index 5e3c6e69f..564fe55b2 100644 --- a/app/views/layouts/mailer.html.erb +++ b/app/views/layouts/mailer.html.erb @@ -63,7 +63,7 @@ background: #2d71e5; border-color: #2d71e5; border-radius: 3rem; - color: #ffffff; + color: #ffffff !important; font-weight: 500; padding: 0.5em 1em; text-decoration: none; diff --git a/app/views/mailers/identity_mailer/email_change_confirmation.html.erb b/app/views/mailers/identity_mailer/email_change_confirmation.html.erb index 438dc18e9..dd55d602e 100644 --- a/app/views/mailers/identity_mailer/email_change_confirmation.html.erb +++ b/app/views/mailers/identity_mailer/email_change_confirmation.html.erb @@ -1,5 +1,4 @@ -

Confirm your email address change

-

Hit the button below to use this email address in Fizzy.

+

Confirm your email address change

<%= link_to "Yes use use this email address", email_address_confirmation_url(membership_id: @membership.id, email_address_token: @token), class: "btn" %> diff --git a/app/views/mailers/magic_link_mailer/sign_in_instructions.html.erb b/app/views/mailers/magic_link_mailer/sign_in_instructions.html.erb index b966ecfa6..6033b6165 100644 --- a/app/views/mailers/magic_link_mailer/sign_in_instructions.html.erb +++ b/app/views/mailers/magic_link_mailer/sign_in_instructions.html.erb @@ -1,9 +1,8 @@ -

Sign in to Fizzy

-

Hit the button below to sign in to Fizzy on this device

+

Hit the button below to confirm your email.

-<%= link_to "Sign in to Fizzy", session_magic_link_url(code: @magic_link.code), class: "btn" %> +<%= link_to "Confirm and return to Fizzy", session_magic_link_url(code: @magic_link.code), class: "btn" %> -

If you’re signing in on another device, enter this special code: +

If Fizzy is on another device, enter this special code instead:
<%= @magic_link.code %>

diff --git a/app/views/mailers/magic_link_mailer/sign_in_instructions.text.erb b/app/views/mailers/magic_link_mailer/sign_in_instructions.text.erb index f44e1c908..215508029 100644 --- a/app/views/mailers/magic_link_mailer/sign_in_instructions.text.erb +++ b/app/views/mailers/magic_link_mailer/sign_in_instructions.text.erb @@ -1,8 +1,6 @@ -Sign in to Fizzy -<%= "=" * 80 %> +Hit the button below to confirm your email: -Open this link in your browser to login on this device: <%= session_magic_link_url(code: @magic_link.code) %> -If you're signing in on another device, enter this code: +If Fizzy is on another device, enter this special code instead: <%= @magic_link.code %> diff --git a/app/views/memberships/email_addresses/new.html.erb b/app/views/memberships/email_addresses/new.html.erb index f88456679..e99596471 100644 --- a/app/views/memberships/email_addresses/new.html.erb +++ b/app/views/memberships/email_addresses/new.html.erb @@ -13,7 +13,7 @@

<%= @page_title %>

-

Enter your new email address. We'll send you a confirmation to verify it.

+

Enter your new email address, then hit the link to confirm it

<%= form_with url: email_addresses_path(membership_id: @membership.id), method: :post, class: "flex flex-column gap", data: { controller: "form", turbo: false } do |form| %> @@ -23,8 +23,8 @@ - <% end %> diff --git a/app/views/sessions/magic_links/show.html.erb b/app/views/sessions/magic_links/show.html.erb index 7af4e6e20..445b3233a 100644 --- a/app/views/sessions/magic_links/show.html.erb +++ b/app/views/sessions/magic_links/show.html.erb @@ -3,7 +3,7 @@
">

<%= @page_title %>

-

We just emailed you a link to sign in.

+

Then hit the link to continue

If your email is on a different device, enter the code included in the email below.

diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index be35c17b1..eb47bfe4f 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -1,4 +1,4 @@ -<% @page_title = "Sign in" %> +<% @page_title = "Enter your email" %>

<%= @page_title %>

diff --git a/app/views/users/joins/new.html.erb b/app/views/users/joins/new.html.erb index adf795ece..a5601b7a7 100644 --- a/app/views/users/joins/new.html.erb +++ b/app/views/users/joins/new.html.erb @@ -1,24 +1,17 @@ -<% @page_title = "Join #{Account.sole.name}" %> +<% @page_title = "Great! Now enter your name" %>
-
-

- <%= @page_title %> -

- -

Enter your name to continue

-
+

<%= @page_title %>

<%= form_with scope: "user", url: users_joins_path(membership: params[:membership]), class: "flex flex-column gap txt-medium", data: { controller: "form" } do |form| %>
<% end %> diff --git a/gems/fizzy-saas/app/views/signup/memberships/new.html.erb b/gems/fizzy-saas/app/views/signup/memberships/new.html.erb index 416e9d8f2..97f253266 100644 --- a/gems/fizzy-saas/app/views/signup/memberships/new.html.erb +++ b/gems/fizzy-saas/app/views/signup/memberships/new.html.erb @@ -1,12 +1,12 @@ -<% @page_title = "Create your account" %> +<% @page_title = "Create your new Fizzy account" %> -
"> -

<%= @page_title %>

+
"> +

<%= @page_title %>

<%= form_with model: @signup, url: saas.signup_membership_path, scope: "signup", class: "flex flex-column gap", data: { controller: "form" } do |form| %> <%= form.hidden_field :new_user %> - <%= form.text_field :full_name, class: "input", autocomplete: "name", placeholder: "Your name", autofocus: true, required: true %> + <%= form.text_field :full_name, class: "input", autocomplete: "name", placeholder: "Full name", autofocus: true, required: true %> <% if @signup.errors.any? %>
From 08a7db935a30eb2a5152b6c3ca5d62a497f5d1dc Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 5 Nov 2025 13:17:30 -0600 Subject: [PATCH 048/175] Fix mailer preview --- .../previews/identity_mailer_preview.rb | 17 +++++++++++++++++ test/mailers/previews/user_mailer_preview.rb | 18 ------------------ 2 files changed, 17 insertions(+), 18 deletions(-) create mode 100644 test/mailers/previews/identity_mailer_preview.rb delete mode 100644 test/mailers/previews/user_mailer_preview.rb diff --git a/test/mailers/previews/identity_mailer_preview.rb b/test/mailers/previews/identity_mailer_preview.rb new file mode 100644 index 000000000..8d29667bc --- /dev/null +++ b/test/mailers/previews/identity_mailer_preview.rb @@ -0,0 +1,17 @@ +class IdentityMailerPreview < ActionMailer::Preview + def email_change_confirmation + ApplicationRecord.current_tenant = "897362094" + + identity = Identity.find_by(email_address: "david@37signals.com") + membership = identity&.memberships&.find_by(tenant: ApplicationRecord.current_tenant) + + new_email_address = "david.new@example.com" + token = membership.send(:generate_email_address_change_token, to: new_email_address) + + IdentityMailer.email_change_confirmation( + email_address: new_email_address, + token: token, + membership: membership + ) + end +end diff --git a/test/mailers/previews/user_mailer_preview.rb b/test/mailers/previews/user_mailer_preview.rb deleted file mode 100644 index d36b8929b..000000000 --- a/test/mailers/previews/user_mailer_preview.rb +++ /dev/null @@ -1,18 +0,0 @@ -class UserMailerPreview < ActionMailer::Preview - def email_change_confirmation - ApplicationRecord.current_tenant = "897362094" - user = User.find_by(email_address: "david@37signals.com") || User.new( - name: "David", - email_address: "david@37signals.com" - ) - - new_email_address = "david.new@example.com" - token = user.generate_email_address_change_token(to: new_email_address) - - UserMailer.email_change_confirmation( - user: user, - email_address: new_email_address, - token: token - ) - end -end From d18c9d38670248b9cf977ed9c71e3c3f747d2a1b Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 5 Nov 2025 13:20:28 -0600 Subject: [PATCH 049/175] No longer used --- app/mailers/user_mailer.rb | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 app/mailers/user_mailer.rb diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb deleted file mode 100644 index 09926c09b..000000000 --- a/app/mailers/user_mailer.rb +++ /dev/null @@ -1,8 +0,0 @@ -class UserMailer < ApplicationMailer - def email_change_confirmation(user:, email_address:, token:) - @user = user - @token = token - - mail to: email_address, subject: "Confirm your new email address" - end -end From db764836e49797893c618c0134bf731553571e20 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 5 Nov 2025 13:21:38 -0600 Subject: [PATCH 050/175] Update test to match copy changes --- test/mailers/magic_link_mailer_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mailers/magic_link_mailer_test.rb b/test/mailers/magic_link_mailer_test.rb index ee3d56abc..0c0dce2b6 100644 --- a/test/mailers/magic_link_mailer_test.rb +++ b/test/mailers/magic_link_mailer_test.rb @@ -10,7 +10,7 @@ class MagicLinkMailerTest < ActionMailer::TestCase end assert_equal [ "kevin@37signals.com" ], email.to - assert_equal "Sign in to Fizzy", email.subject + assert_equal "Confirm your email", email.subject assert_match magic_link.code, email.body.encoded end end From 3a21e0ed82d0bca00a6160495f24d42073e5cb5b Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 5 Nov 2025 17:18:37 -0600 Subject: [PATCH 051/175] Stronger logo presence (bigger) --- app/views/layouts/public.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/public.html.erb b/app/views/layouts/public.html.erb index 1373a87d5..6dbd5d61f 100644 --- a/app/views/layouts/public.html.erb +++ b/app/views/layouts/public.html.erb @@ -6,9 +6,9 @@