From 1487f2bd26b2d59baee6274ae38c5d3128004506 Mon Sep 17 00:00:00 2001 From: petzbloom-sudo Date: Fri, 12 Dec 2025 16:43:43 +0000 Subject: [PATCH 01/65] Update _card.json.jbuilder Jbuilder: Include assignees in card data --- app/views/cards/_card.json.jbuilder | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/cards/_card.json.jbuilder b/app/views/cards/_card.json.jbuilder index a1c509a66..69833cc22 100644 --- a/app/views/cards/_card.json.jbuilder +++ b/app/views/cards/_card.json.jbuilder @@ -15,6 +15,7 @@ json.cache! card do json.board card.board, partial: "boards/board", as: :board json.column card.column, partial: "columns/column", as: :column if card.column json.creator card.creator, partial: "users/user", as: :user + json.assignees card.assignees, partial: "users/user", as: :user json.comments_url card_comments_url(card) end From b3cfae352975ddcaf4031cd6cfa4de842e8ca0ec Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 12 Dec 2025 12:29:01 -0600 Subject: [PATCH 02/65] Consolidate UI elements from "the stream" and "Considering" to "Maybe?" --- app/assets/stylesheets/_global.css | 2 +- app/assets/stylesheets/blank-slates.css | 2 +- app/assets/stylesheets/card-columns.css | 16 ++++++++-------- app/assets/stylesheets/print.css | 4 ++-- app/views/boards/show/_columns.html.erb | 2 +- .../show/{_stream.html.erb => _maybe.html.erb} | 4 ++-- app/views/boards/show/_mobile_columns.html.erb | 2 +- .../cards/drops/streams/create.turbo_stream.erb | 2 +- app/views/public/boards/show/_columns.html.erb | 2 +- app/views/public/boards/show/_stream.html.erb | 2 +- 10 files changed, 19 insertions(+), 19 deletions(-) rename app/views/boards/show/{_stream.html.erb => _maybe.html.erb} (76%) diff --git a/app/assets/stylesheets/_global.css b/app/assets/stylesheets/_global.css index dc928b40d..213d921e4 100644 --- a/app/assets/stylesheets/_global.css +++ b/app/assets/stylesheets/_global.css @@ -205,7 +205,7 @@ --color-terminal-text: var(--color-ink); --color-terminal-text-light: var(--color-ink-lighter); --color-golden: oklch(89.1% 0.178 95.7); - --color-considering: oklch(var(--lch-blue-medium)); + --color-maybe: oklch(var(--lch-blue-medium)); /* Colors: Cards */ --color-card-default: oklch(var(--lch-blue-dark)); diff --git a/app/assets/stylesheets/blank-slates.css b/app/assets/stylesheets/blank-slates.css index 0725dd336..3c36b30da 100644 --- a/app/assets/stylesheets/blank-slates.css +++ b/app/assets/stylesheets/blank-slates.css @@ -11,7 +11,7 @@ white-space: nowrap; } - .cards--considering & { + .cards--maybe & { background-color: var(--card-bg-color) !important; } diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index c239e1b9d..6d6011906 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -128,7 +128,7 @@ translate: 0; transition: translate var(--column-transition-duration) var(--ease-out-overshoot-subtle); - .is-collapsed:not(.cards--considering) & { + .is-collapsed:not(.cards--maybe) & { translate: 0 var(--column-width-collapsed); } @@ -354,7 +354,7 @@ justify-content: center; } - .cards:is(.cards--considering) &:hover { + .cards:is(.cards--maybe) &:hover { cursor: unset; filter: none; } @@ -382,7 +382,7 @@ writing-mode: vertical-rl; } - .cards:not(.is-collapsed, .cards--considering) & { + .cards:not(.is-collapsed, .cards--maybe) & { align-items: center; display: flex; gap: 0.25ch; @@ -523,11 +523,11 @@ } } - /* Considering + /* Maybe /* ------------------------------------------------------------------------ */ - .cards--considering { - --card-color: oklch(var(--lch-blue-medium)); + .cards--maybe { + --card-color: var(--color-maybe); position: relative; @@ -664,7 +664,7 @@ /* -------------------------------------------------------------------------- */ /* Surface a mini bubble if there are cards with bubbles inside */ - .cards--considering:has(.bubble:not([hidden])) .cards__expander-title, + .cards--maybe:has(.bubble:not([hidden])) .cards__expander-title, .cards--doing.is-collapsed:has(.bubble:not([hidden])) .cards__transition-container { --bubble-color: var(--card-color, oklch(var(--lch-blue-medium))); --bubble-shape: 54% 46% 61% 39% / 57% 49% 51% 43%; @@ -685,7 +685,7 @@ } /* Maybe column: position bubble relative to the title, not the container */ - .cards--considering & { + .cards--maybe & { overflow: visible; position: relative; diff --git a/app/assets/stylesheets/print.css b/app/assets/stylesheets/print.css index c79493458..f700b9332 100644 --- a/app/assets/stylesheets/print.css +++ b/app/assets/stylesheets/print.css @@ -125,7 +125,7 @@ .card--new, .cards__decoration, .card-columns:before, - .cards--considering:before { + .cards--maybe:before { display: none; } @@ -140,7 +140,7 @@ padding-inline: 0; } - .cards--considering { + .cards--maybe { background: none; margin: 0; padding-inline: 1ch; diff --git a/app/views/boards/show/_columns.html.erb b/app/views/boards/show/_columns.html.erb index a6001da79..34f926bfc 100644 --- a/app/views/boards/show/_columns.html.erb +++ b/app/views/boards/show/_columns.html.erb @@ -23,7 +23,7 @@ <%= render "boards/show/not_now", board: board %> - <%= render "boards/show/stream", board: board, page: page %> + <%= render "boards/show/maybe", board: board, page: page %>
<%= render partial: "boards/show/column", collection: board.columns.sorted, cached: ->(column){ [ column, column.leftmost?, column.rightmost? ] } %> diff --git a/app/views/boards/show/_stream.html.erb b/app/views/boards/show/_maybe.html.erb similarity index 76% rename from app/views/boards/show/_stream.html.erb rename to app/views/boards/show/_maybe.html.erb index d4753eba5..6fb46b7b5 100644 --- a/app/views/boards/show/_stream.html.erb +++ b/app/views/boards/show/_maybe.html.erb @@ -1,4 +1,4 @@ -<%= column_tag id: "the-stream", name: "Maybe?", drop_url: columns_card_drops_stream_path("__id__"), collapsed: false, selected: "true", class: "cards--considering" do %> +<%= column_tag id: "maybe", name: "Maybe?", drop_url: columns_card_drops_stream_path("__id__"), collapsed: false, selected: "true", class: "cards--maybe" do %>

Maybe?

@@ -10,7 +10,7 @@
<%= render "columns/show/add_card_button", board: board %> <% if page.used? %> - <%= with_automatic_pagination "the-stream", @page do %> + <%= with_automatic_pagination "maybe", @page do %> <%= render "boards/columns/list", cards: page.records, draggable: true %> <% end %> <% else %> diff --git a/app/views/boards/show/_mobile_columns.html.erb b/app/views/boards/show/_mobile_columns.html.erb index 964d75220..e8f5938ae 100644 --- a/app/views/boards/show/_mobile_columns.html.erb +++ b/app/views/boards/show/_mobile_columns.html.erb @@ -12,7 +12,7 @@
<% end %> - <%= link_to board_columns_stream_path(board), class: "cards cards--considering is-collapsed", data: { turbo_frame: "_top" } do %> + <%= link_to board_columns_stream_path(board), class: "cards cards--maybe is-collapsed", data: { turbo_frame: "_top" } do %>
<%= board.cards.awaiting_triage.count %>

diff --git a/app/views/columns/cards/drops/streams/create.turbo_stream.erb b/app/views/columns/cards/drops/streams/create.turbo_stream.erb index 3f6251645..3141b847f 100644 --- a/app/views/columns/cards/drops/streams/create.turbo_stream.erb +++ b/app/views/columns/cards/drops/streams/create.turbo_stream.erb @@ -1 +1 @@ -<%= turbo_stream.replace("the-stream", partial: "boards/show/stream", method: :morph, locals:{ board: @card.board, page: @page }) %> +<%= turbo_stream.replace("maybe", partial: "boards/show/maybe", method: :morph, locals:{ board: @card.board, page: @page }) %> diff --git a/app/views/public/boards/show/_columns.html.erb b/app/views/public/boards/show/_columns.html.erb index 59f2b5e39..de0948491 100644 --- a/app/views/public/boards/show/_columns.html.erb +++ b/app/views/public/boards/show/_columns.html.erb @@ -10,7 +10,7 @@ <%= render "public/boards/show/not_now", board: board %>

- <%= render "public/boards/show/stream", board: board, page: page %> + <%= render "public/boards/show/maybe", board: board, page: page %>
<%= render partial: "public/boards/show/column", collection: board.columns, cached: true %> diff --git a/app/views/public/boards/show/_stream.html.erb b/app/views/public/boards/show/_stream.html.erb index 8fea976b6..9d041c1cd 100644 --- a/app/views/public/boards/show/_stream.html.erb +++ b/app/views/public/boards/show/_stream.html.erb @@ -1,4 +1,4 @@ -
From 3b08041a3e6e887a512cf3e9b1a8af97adf2ef28 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 12 Dec 2025 13:16:45 -0600 Subject: [PATCH 03/65] Only use grid layout when centering the Maybe column --- app/assets/stylesheets/card-columns.css | 60 +++++++++---------- .../collapsible_columns_controller.js | 4 +- app/views/boards/show/_columns.html.erb | 1 + app/views/boards/show/_maybe.html.erb | 15 +++-- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 6d6011906..bd1ff3db6 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -19,9 +19,9 @@ --progress-max-height: 50dvh; container-type: inline-size; - display: grid; + display: flex; + justify-content: center; gap: var(--column-gap); - grid-template-columns: 1fr var(--column-width-expanded) 1fr; margin-inline: auto; max-inline-size: var(--main-width); overflow-x: auto; @@ -29,37 +29,39 @@ padding-block-end: var(--column-width-collapsed); position: relative; - /* When it has something expanded */ - &:has(.card-columns__left .cards:not(.is-collapsed), .card-columns__right .cards:not(.is-collapsed)) { - grid-template-columns: auto var(--column-width-expanded) auto; - } - &:has(.cards) { min-block-size: 20lh; } - - body:not(.public) & { - @media (max-width: 519px) { - display: none; - } - } } .card-columns__left, .card-columns__right { - align-items: stretch; - display: flex; - gap: var(--column-gap); + display: contents; } - .card-columns__left { - justify-content: end; - padding-inline-start: var(--column-gap); - } + /* Center the Maybe? column when all the other columns are collapsed */ + @media (min-width: 640px) { + .card-columns:not(:has(.card-columns__left .is-expanded, .card-columns__right .is-expanded)) { + display: grid; + grid-template-columns: 1fr auto 1fr; - .card-columns__right { - justify-content: start; - padding-inline-end: var(--column-gap); + .card-columns__left, + .card-columns__right { + align-items: stretch; + display: flex; + gap: var(--column-gap); + } + + .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 @@ -128,7 +130,7 @@ translate: 0; transition: translate var(--column-transition-duration) var(--ease-out-overshoot-subtle); - .is-collapsed:not(.cards--maybe) & { + .is-collapsed & { translate: 0 var(--column-width-collapsed); } @@ -353,11 +355,6 @@ inline-size: 100%; justify-content: center; } - - .cards:is(.cards--maybe) &:hover { - cursor: unset; - filter: none; - } } .cards__expander-count { @@ -382,7 +379,7 @@ writing-mode: vertical-rl; } - .cards:not(.is-collapsed, .cards--maybe) & { + .cards:not(.is-collapsed) & { align-items: center; display: flex; gap: 0.25ch; @@ -691,7 +688,8 @@ &:before { inset-block-start: 50%; - translate: 125% -75%; + inset-inline-start: 0; + translate: -125% -75%; z-index: -1; } } diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index 2c97fcd09..5622bb24a 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -2,7 +2,7 @@ import { Controller } from "@hotwired/stimulus" import { nextFrame, debounce } from "helpers/timing_helpers"; export default class extends Controller { - static classes = [ "collapsed", "noTransitions", "titleNotVisible" ] + static classes = [ "collapsed", "expanded", "noTransitions", "titleNotVisible" ] static targets = [ "column", "button", "title" ] static values = { board: String @@ -89,6 +89,7 @@ export default class extends Controller { const key = this.#localStorageKeyFor(column) this.#buttonFor(column).setAttribute("aria-expanded", "false") + column.classList.remove(this.expandedClass) column.classList.add(this.collapsedClass) localStorage.removeItem(key) } @@ -98,6 +99,7 @@ export default class extends Controller { this.#buttonFor(column).setAttribute("aria-expanded", "true") column.classList.remove(this.collapsedClass) + column.classList.add(this.expandedClass) localStorage.setItem(key, true) } diff --git a/app/views/boards/show/_columns.html.erb b/app/views/boards/show/_columns.html.erb index 34f926bfc..cb7d4c086 100644 --- a/app/views/boards/show/_columns.html.erb +++ b/app/views/boards/show/_columns.html.erb @@ -4,6 +4,7 @@ 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_expanded_class: "is-expanded", collapsible_columns_no_transitions_class: "no-transitions", collapsible_columns_title_not_visible_class: "is-off-screen", navigable_list_supports_vertical_navigation_value: false, diff --git a/app/views/boards/show/_maybe.html.erb b/app/views/boards/show/_maybe.html.erb index 6fb46b7b5..4ef7c4a01 100644 --- a/app/views/boards/show/_maybe.html.erb +++ b/app/views/boards/show/_maybe.html.erb @@ -1,12 +1,11 @@ -<%= column_tag id: "maybe", name: "Maybe?", drop_url: columns_card_drops_stream_path("__id__"), collapsed: false, selected: "true", class: "cards--maybe" do %> +<%= column_tag id: "maybe", name: "Maybe?", drop_url: columns_card_drops_stream_path("__id__"), collapsed: false, selected: "true", class: "cards--maybe", data: { + drag_and_strum_target: "container", + collapsible_columns_target: "column", + action: "focus->collapsible-columns#focusOnColumn" +} do %>
-
-

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 "grid", class: "translucent" %> - Expand column - <% end %> + <%= render "boards/show/expander", title: "Maybe?", count: board.cards.awaiting_triage.count, column_id: "maybe" %> + <%= render "boards/show/menu/maximize", column_path: board_columns_stream_path(board) %>
<%= render "columns/show/add_card_button", board: board %> <% if page.used? %> From c6bfeb6982eb85df26d736d1b8eedc01731e182b Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 12 Dec 2025 13:49:17 -0600 Subject: [PATCH 04/65] Handle Maybe column state at desktop breakpoint --- app/assets/stylesheets/card-columns.css | 10 ++++++- .../collapsible_columns_controller.js | 30 +++++++++++++++++-- app/views/boards/show/_maybe.html.erb | 2 +- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index bd1ff3db6..59edac129 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -297,8 +297,16 @@ position: relative; text-transform: uppercase; + &[disabled] { + opacity: 1; + } + @media (any-hover: hover) { - &:hover { + .is-expanded &:hover { + filter: none; + } + + .is-collapsed &:hover { filter: brightness(0.9); } } diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index 5622bb24a..bf0ff9654 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -3,9 +3,10 @@ import { nextFrame, debounce } from "helpers/timing_helpers"; export default class extends Controller { static classes = [ "collapsed", "expanded", "noTransitions", "titleNotVisible" ] - static targets = [ "column", "button", "title" ] + static targets = [ "column", "button", "title", "maybeColumn" ] static values = { - board: String + board: String, + desktopBreakpoint: { type: String, default: "(min-width: 640px)" } } initialize() { @@ -15,6 +16,11 @@ export default class extends Controller { async connect() { await this.#restoreColumnsDisablingTransitions() this.#setupIntersectionObserver() + + this.mediaQuery = window.matchMedia(this.desktopBreakpointValue) + this.handleDesktop = this.#handleDesktop.bind(this) + this.mediaQuery.addEventListener("change", this.handleDesktop) + this.handleDesktop(this.mediaQuery) } disconnect() { @@ -22,6 +28,7 @@ export default class extends Controller { this._intersectionObserver.disconnect() this._intersectionObserver = null } + this.mediaQuery.removeEventListener("change", this.handleDesktop) } toggle({ target }) { @@ -142,4 +149,23 @@ export default class extends Controller { this.titleTargets.forEach(title => this._intersectionObserver.observe(title)) } + + #handleDesktop(e) { + const matches = e.matches ?? e // handle both Event and MediaQueryList + matches ? this.#disableMaybeToggle() : this.#enableMaybeToggle() + } + + #disableMaybeToggle() { + this.#expand(this.maybeColumnTarget) + this.#maybeButton.setAttribute("disabled", true) + } + + #enableMaybeToggle() { + this.#collapse(this.maybeColumnTarget) + this.#maybeButton.removeAttribute("disabled") + } + + get #maybeButton() { + return this.maybeColumnTarget.querySelector('[data-collapsible-columns-target="button"]') + } } diff --git a/app/views/boards/show/_maybe.html.erb b/app/views/boards/show/_maybe.html.erb index 4ef7c4a01..b060c357d 100644 --- a/app/views/boards/show/_maybe.html.erb +++ b/app/views/boards/show/_maybe.html.erb @@ -1,6 +1,6 @@ <%= column_tag id: "maybe", name: "Maybe?", drop_url: columns_card_drops_stream_path("__id__"), collapsed: false, selected: "true", class: "cards--maybe", data: { drag_and_strum_target: "container", - collapsible_columns_target: "column", + collapsible_columns_target: "column maybeColumn", action: "focus->collapsible-columns#focusOnColumn" } do %>
From b80499e955ca021757bbe55d191e6b8019afbe11 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 12 Dec 2025 14:08:22 -0600 Subject: [PATCH 05/65] Leave Maybe column expanded on Desktop --- .../collapsible_columns_controller.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index bf0ff9654..cd533220e 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -81,7 +81,9 @@ export default class extends Controller { } #collapseAllExcept(clickedColumn) { - this.columnTargets.forEach(column => { + const columns = this.#isDesktop ? this.columnTargets.filter(c => c !== this.maybeColumnTarget) : this.columnTargets + + columns.forEach(column => { if (column !== clickedColumn) { this.#collapse(column) } @@ -150,18 +152,21 @@ export default class extends Controller { this.titleTargets.forEach(title => this._intersectionObserver.observe(title)) } - #handleDesktop(e) { - const matches = e.matches ?? e // handle both Event and MediaQueryList - matches ? this.#disableMaybeToggle() : this.#enableMaybeToggle() + #isDesktop(e) { + return e.matches ?? e } - #disableMaybeToggle() { + #handleDesktop(e) { + this.#isDesktop(e) ? this.#handleDesktopMode() : this.#handleMobileMode() + } + + async #handleDesktopMode() { this.#expand(this.maybeColumnTarget) this.#maybeButton.setAttribute("disabled", true) } - #enableMaybeToggle() { - this.#collapse(this.maybeColumnTarget) + #handleMobileMode() { + this.columnTargets.forEach(column => this.#collapse(column)) this.#maybeButton.removeAttribute("disabled") } From a94f6e8f0d655cc23867856a9c97e9bd25d23504 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 12 Dec 2025 14:40:20 -0600 Subject: [PATCH 06/65] Remove old mobile columns stuff --- app/assets/stylesheets/card-columns.css | 47 ++----------------- app/views/boards/show.html.erb | 5 +- .../boards/show/_mobile_columns.html.erb | 43 ----------------- 3 files changed, 7 insertions(+), 88 deletions(-) delete mode 100644 app/views/boards/show/_mobile_columns.html.erb diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 59edac129..a3f4f0e36 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -738,53 +738,12 @@ } } - /* Mobile columns + /* Mobile stuff /* -------------------------------------------------------------------------- */ - .mobile-card-columns { - --column-gap: 8px; - --column-padding-expanded: calc(var(--column-gap) * 2); - --column-width-collapsed: 40px; - --progress-increment: var(--progress-max-width) / var(--progress-max-cards); - --progress-max-cards: 20; - --progress-max-width: 100%; - - padding-inline: 3vw; - - /* Hide on larger devices with cursors */ - @media (min-width: 520px) { + @media (min-width: 640px) { + .hide-on-desktop { display: none; } - - .cards.is-collapsed { - inline-size: auto; - display: block; - padding-block: 0.5ch; - - .cards__expander { - --gradient-direction: to right; - - flex-direction: row; - inline-size: auto; - - &:before { - block-size: 1px; - inline-size: 100%; - inset: 50% 0 auto; - translate: 0 -50%; - } - - &:after { - block-size: var(--column-width-collapsed); - inline-size: calc(var(--column-width-collapsed) + var(--card-count) * var(--progress-increment)); - margin-inline-start: 0; - max-inline-size: var(--progress-max-width); - } - } - - .cards__expander-title { - writing-mode: revert; - } - } } } diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb index 9f0c2ebee..c770a9b00 100644 --- a/app/views/boards/show.html.erb +++ b/app/views/boards/show.html.erb @@ -21,11 +21,14 @@ <%= hidden_field_tag "board_ids[]", @board.id %> <% end %> +
+ <%= render "columns/show/add_card_button", board: @board %> +
+ <%= turbo_frame_tag :cards_container do %> <% if @filter.used?(ignore_boards: true) %> <%= render "boards/show/filtered_cards", page: @page %> <% else %> <%= render "boards/show/columns", page: @page, board: @board %> - <%= render "boards/show/mobile_columns", page: @page, board: @board %> <% end %> <% end %> diff --git a/app/views/boards/show/_mobile_columns.html.erb b/app/views/boards/show/_mobile_columns.html.erb deleted file mode 100644 index e8f5938ae..000000000 --- a/app/views/boards/show/_mobile_columns.html.erb +++ /dev/null @@ -1,43 +0,0 @@ -
-
- <%= render "columns/show/add_card_button", board: board %> -
- - <%= link_to board_columns_not_now_path(board), class: "cards cards--on-deck is-collapsed", style: "--card-color: var(--color-card-complete);", data: { turbo_frame: "_top" } do %> -
- <%= board.cards.postponed.count %> -

- Not Now -

-
- <% end %> - - <%= link_to board_columns_stream_path(board), class: "cards cards--maybe is-collapsed", data: { turbo_frame: "_top" } do %> -
- <%= board.cards.awaiting_triage.count %> -

- Maybe? -

-
- <% end %> - - <% board.columns.sorted.each do |column| %> - <%= link_to board_column_path(column.board, column), class: "cards cards--doing is-collapsed", style: "--card-color: #{column.color}", data: { turbo_frame: "_top" } do %> -
- <%= column.cards.active.count %> -

- <%= column.name %> -

-
- <% end %> - <% end %> - - <%= link_to board_columns_closed_path(board), class: "cards cards--closed is-collapsed", style: "--card-color: var(--color-card-complete);", data: { turbo_frame: "_top" } do %> -
- <%= board.cards.closed.count %> -

- Done -

-
- <% end %> -
From c97973149196356f7d1014539a134ac8be89042c Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 12 Dec 2025 14:46:04 -0600 Subject: [PATCH 07/65] Use getter for isDesktop --- .../controllers/collapsible_columns_controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index cd533220e..26950f36a 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -152,12 +152,12 @@ export default class extends Controller { this.titleTargets.forEach(title => this._intersectionObserver.observe(title)) } - #isDesktop(e) { - return e.matches ?? e + get #isDesktop() { + return this.mediaQuery?.matches } - #handleDesktop(e) { - this.#isDesktop(e) ? this.#handleDesktopMode() : this.#handleMobileMode() + #handleDesktop() { + this.#isDesktop ? this.#handleDesktopMode() : this.#handleMobileMode() } async #handleDesktopMode() { From 0d370a04a2ad1c59e0f52d6f6a777e3fc568afde Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 12 Dec 2025 14:49:13 -0600 Subject: [PATCH 08/65] Fix .closest selector to be more fuzzy --- app/javascript/controllers/collapsible_columns_controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index 26950f36a..e9974b0ce 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -32,7 +32,7 @@ export default class extends Controller { } toggle({ target }) { - const column = target.closest('[data-collapsible-columns-target="column"]') + const column = target.closest('[data-collapsible-columns-target~="column"]') this.#toggleColumn(column); } From 44e7e662a8d2903ee9f70ee12bae6c0e146f2539 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 12 Dec 2025 14:54:03 -0600 Subject: [PATCH 09/65] Adjust padding on new card element --- app/assets/stylesheets/card-columns.css | 6 ++++-- app/views/boards/show.html.erb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index a3f4f0e36..165208e0f 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -741,8 +741,10 @@ /* Mobile stuff /* -------------------------------------------------------------------------- */ - @media (min-width: 640px) { - .hide-on-desktop { + .mobile-add-card-container { + padding-inline: 1rem; + + @media (min-width: 640px) { display: none; } } diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb index c770a9b00..e9d3bbe51 100644 --- a/app/views/boards/show.html.erb +++ b/app/views/boards/show.html.erb @@ -21,7 +21,7 @@ <%= hidden_field_tag "board_ids[]", @board.id %> <% end %> -
+
<%= render "columns/show/add_card_button", board: @board %>
From bf62c97c25f811011468a0a8aabce73f05b89160 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 12 Dec 2025 16:18:47 -0600 Subject: [PATCH 10/65] Fix horizontal scrolling --- app/assets/stylesheets/card-columns.css | 109 +++++++++++++++--------- app/views/boards/show.html.erb | 4 - 2 files changed, 71 insertions(+), 42 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 165208e0f..691284c8c 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -19,49 +19,48 @@ --progress-max-height: 50dvh; container-type: inline-size; - display: flex; - justify-content: center; + display: grid; gap: var(--column-gap); + grid-template-columns: 1fr auto 1fr; margin-inline: auto; max-inline-size: var(--main-width); + outline: none; overflow-x: auto; overflow-y: hidden; padding-block-end: var(--column-width-collapsed); position: relative; + /* When it has something expanded */ + &:has(.card-columns__left .cards:not(.is-collapsed), .card-columns__right .cards:not(.is-collapsed)) { + grid-template-columns: auto var(--column-width-expanded) auto; + } + &:has(.cards) { min-block-size: 20lh; } + + body:not(.public) & { + @media (max-width: 519px) { + display: none; + } + } } .card-columns__left, .card-columns__right { - display: contents; + align-items: stretch; + display: flex; + gap: var(--column-gap); } - /* Center the Maybe? column when all the other columns are collapsed */ - @media (min-width: 640px) { - .card-columns:not(:has(.card-columns__left .is-expanded, .card-columns__right .is-expanded)) { - display: grid; - grid-template-columns: 1fr auto 1fr; + .card-columns__left { + justify-content: end; + padding-inline-start: var(--column-gap); + } - .card-columns__left, - .card-columns__right { - align-items: stretch; - display: flex; - gap: var(--column-gap); - } - - .card-columns__left { - justify-content: end; - padding-inline-start: var(--column-gap); - } - - .card-columns__right { - justify-content: start; - padding-inline-end: var(--column-gap); - } - } + .card-columns__right { + justify-content: start; + padding-inline-end: var(--column-gap); } /* Column @@ -302,11 +301,7 @@ } @media (any-hover: hover) { - .is-expanded &:hover { - filter: none; - } - - .is-collapsed &:hover { + .is-collapsed:hover { filter: brightness(0.9); } } @@ -528,11 +523,11 @@ } } - /* Maybe + /* Considering /* ------------------------------------------------------------------------ */ .cards--maybe { - --card-color: var(--color-maybe); + --card-color: oklch(var(--lch-blue-medium)); position: relative; @@ -696,8 +691,7 @@ &:before { inset-block-start: 50%; - inset-inline-start: 0; - translate: -125% -75%; + translate: 125% -75%; z-index: -1; } } @@ -738,14 +732,53 @@ } } - /* Mobile stuff + /* Mobile columns /* -------------------------------------------------------------------------- */ - .mobile-add-card-container { - padding-inline: 1rem; + .mobile-card-columns { + --column-gap: 8px; + --column-padding-expanded: calc(var(--column-gap) * 2); + --column-width-collapsed: 40px; + --progress-increment: var(--progress-max-width) / var(--progress-max-cards); + --progress-max-cards: 20; + --progress-max-width: 100%; - @media (min-width: 640px) { + padding-inline: 3vw; + + /* Hide on larger devices with cursors */ + @media (min-width: 520px) { display: none; } + + .cards.is-collapsed { + inline-size: auto; + display: block; + padding-block: 0.5ch; + + .cards__expander { + --gradient-direction: to right; + + flex-direction: row; + inline-size: auto; + + &:before { + block-size: 1px; + inline-size: 100%; + inset: 50% 0 auto; + translate: 0 -50%; + } + + &:after { + block-size: var(--column-width-collapsed); + inline-size: calc(var(--column-width-collapsed) + var(--card-count) * var(--progress-increment)); + margin-inline-start: 0; + max-inline-size: var(--progress-max-width); + } + } + + .cards__expander-title { + writing-mode: revert; + } + } } } diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb index e9d3bbe51..bd3266ac3 100644 --- a/app/views/boards/show.html.erb +++ b/app/views/boards/show.html.erb @@ -21,10 +21,6 @@ <%= hidden_field_tag "board_ids[]", @board.id %> <% end %> -
- <%= render "columns/show/add_card_button", board: @board %> -
- <%= turbo_frame_tag :cards_container do %> <% if @filter.used?(ignore_boards: true) %> <%= render "boards/show/filtered_cards", page: @page %> From 98b47ede6b1db7a9dc87171cac9b58200fa2ec99 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 12 Dec 2025 16:27:06 -0600 Subject: [PATCH 11/65] Center and scroll snap columns on mobile --- app/assets/stylesheets/card-columns.css | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 691284c8c..1225497a0 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -31,18 +31,19 @@ position: relative; /* When it has something expanded */ - &:has(.card-columns__left .cards:not(.is-collapsed), .card-columns__right .cards:not(.is-collapsed)) { - grid-template-columns: auto var(--column-width-expanded) auto; + &:has(.card-columns__left .is-expanded, .card-columns__right .is-expanded) { + grid-template-columns: auto auto auto; } &:has(.cards) { min-block-size: 20lh; } - body:not(.public) & { - @media (max-width: 519px) { - display: none; - } + @media (max-width: 639px) { + --column-padding-expanded: 0px; + --column-width-expanded: calc(100vw - var(--column-gap) * 4); + + scroll-snap-type: inline mandatory; } } @@ -74,6 +75,10 @@ position: relative; scroll-snap-align: start; + @media (max-width: 639px) { + scroll-snap-align: center; + } + &.is-collapsed { inline-size: var(--column-width-collapsed); @@ -691,7 +696,8 @@ &:before { inset-block-start: 50%; - translate: 125% -75%; + inset-inline-start: 0; + translate: -125% -75%; z-index: -1; } } From 5d9b2b8663062198c38ff8a5c557feac8f981049 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 12 Dec 2025 16:30:45 -0600 Subject: [PATCH 12/65] Center all columns when collapsed on mobile --- app/assets/stylesheets/card-columns.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 1225497a0..4ec766882 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -44,6 +44,10 @@ --column-width-expanded: calc(100vw - var(--column-gap) * 4); scroll-snap-type: inline mandatory; + + &:not(:has(.is-expanded)) { + grid-template-columns: auto var(--column-width-collapsed) auto; + } } } From ffb5474a79a51efe0ad67dbc7c9b1cb6d5a4a66c Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 15 Dec 2025 14:40:28 -0600 Subject: [PATCH 13/65] Equal padding on left mobile columns --- app/assets/stylesheets/card-columns.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 4ec766882..e67d4d97d 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -33,6 +33,10 @@ /* When it has something expanded */ &:has(.card-columns__left .is-expanded, .card-columns__right .is-expanded) { grid-template-columns: auto auto auto; + + @media (min-width: 640px) { + grid-template-columns: auto var(--column-width-expanded) auto; + } } &:has(.cards) { @@ -61,6 +65,10 @@ .card-columns__left { justify-content: end; padding-inline-start: var(--column-gap); + + @media (max-width: 639px) { + padding-inline-start: calc(var(--column-gap) * 2); + } } .card-columns__right { From f2591e7da6211002c2df7833899a47af544ecfdd Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 15 Dec 2025 14:56:27 -0600 Subject: [PATCH 14/65] Basic scrolling setup --- app/assets/stylesheets/base.css | 3 ++- app/assets/stylesheets/card-columns.css | 25 +++++++++++++++++++++++++ app/assets/stylesheets/layout.css | 15 ++++++++++----- app/assets/stylesheets/utilities.css | 6 ------ app/views/boards/show.html.erb | 1 + app/views/layouts/application.html.erb | 24 +++++++++++++----------- 6 files changed, 51 insertions(+), 23 deletions(-) diff --git a/app/assets/stylesheets/base.css b/app/assets/stylesheets/base.css index 05d2b016e..689b80215 100644 --- a/app/assets/stylesheets/base.css +++ b/app/assets/stylesheets/base.css @@ -102,7 +102,8 @@ } /* Turbo */ - turbo-frame { + turbo-frame, + turbo-cable-stream-source { display: contents; } diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index e67d4d97d..dabdd69e5 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -1,4 +1,27 @@ @layer components { + /* Layout adjustments for contained scrolling + /* ------------------------------------------------------------------------ */ + + body.contained-scrolling { + block-size: 100dvh; + grid-template-rows: 1fr var(--footer-height); + + #global-container, + #main { + display: grid; + grid-template-rows: auto 1fr; + } + + #global-container { + overflow: hidden; + } + + #main { + overflow: auto; + padding: 0; + } + } + /* Column container /* ------------------------------------------------------------------------ */ @@ -22,6 +45,7 @@ display: grid; gap: var(--column-gap); grid-template-columns: 1fr auto 1fr; + inline-size: 100%; margin-inline: auto; max-inline-size: var(--main-width); outline: none; @@ -88,6 +112,7 @@ scroll-snap-align: start; @media (max-width: 639px) { + overflow-y: auto; scroll-snap-align: center; } diff --git a/app/assets/stylesheets/layout.css b/app/assets/stylesheets/layout.css index e82992dfc..6ea479eb6 100644 --- a/app/assets/stylesheets/layout.css +++ b/app/assets/stylesheets/layout.css @@ -8,6 +8,16 @@ } } + /* Required for the card column page on mobile, but not needed otherwise */ + :where(#global-container) { + display: contents; + } + + :where(#header) { + position: relative; + z-index: var(--z-nav); + } + :where(#main) { inline-size: 100dvw; margin-inline: auto; @@ -22,11 +32,6 @@ max-inline-size: 100dvw; } - :where(#header) { - position: relative; - z-index: var(--z-nav); - } - :is(#header, #footer) { @media print { display: none; diff --git a/app/assets/stylesheets/utilities.css b/app/assets/stylesheets/utilities.css index 0f487e1f9..80cbfcdca 100644 --- a/app/assets/stylesheets/utilities.css +++ b/app/assets/stylesheets/utilities.css @@ -95,12 +95,6 @@ .overflow-clip { text-overflow: clip; white-space: nowrap; overflow: hidden; } .overflow-ellipsis { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } - .overflow-hide-scrollbar::-webkit-scrollbar { - @media (pointer: course) { - display: none; - } - } - .overflow-line-clamp { -webkit-line-clamp: var(--lines, 2); -webkit-box-orient: vertical; diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb index bd3266ac3..e1b0a3e96 100644 --- a/app/views/boards/show.html.erb +++ b/app/views/boards/show.html.erb @@ -1,4 +1,5 @@ <% @page_title = @board.name %> +<% @body_class = "contained-scrolling" %> <% turbo_exempts_page_from_cache %> <%= turbo_stream_from @board %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 6aef1f1bc..ffc7f271f 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -2,19 +2,21 @@ <%= render "layouts/shared/head" %> - - + +
+ - <%= render "layouts/shared/flash" %> - <%= render "layouts/shared/time_zone" if Current.user %> + <%= render "layouts/shared/flash" %> + <%= render "layouts/shared/time_zone" if Current.user %> -
- <%= yield %> -
+
+ <%= yield %> +
+
<%= yield :footer %> From adceae2e7154e5d9d856e0dc5d8645285fd1ef86 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 15 Dec 2025 15:13:14 -0600 Subject: [PATCH 15/65] Scrolling in place (borked padding) --- app/assets/stylesheets/card-columns.css | 39 +++++++++++++++---------- app/views/boards/columns/_list.html.erb | 2 +- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index dabdd69e5..48ce031c0 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -2,23 +2,26 @@ /* Layout adjustments for contained scrolling /* ------------------------------------------------------------------------ */ - body.contained-scrolling { - block-size: 100dvh; - grid-template-rows: 1fr var(--footer-height); + /* Scroll columns individually on mobile */ + @media (max-width: 639px) { + body.contained-scrolling { + block-size: 100dvh; + grid-template-rows: 1fr var(--footer-height); - #global-container, - #main { - display: grid; - grid-template-rows: auto 1fr; - } + #global-container, + #main { + display: grid; + grid-template-rows: auto 1fr; + } - #global-container { - overflow: hidden; - } + #global-container { + overflow: hidden; + } - #main { - overflow: auto; - padding: 0; + #main { + overflow: auto; + padding: 0; + } } } @@ -51,7 +54,6 @@ outline: none; overflow-x: auto; overflow-y: hidden; - padding-block-end: var(--column-width-collapsed); position: relative; /* When it has something expanded */ @@ -77,6 +79,10 @@ grid-template-columns: auto var(--column-width-collapsed) auto; } } + + @media (min-width: 640px) { + padding-block-end: var(--column-width-collapsed); + } } .card-columns__left, @@ -112,6 +118,7 @@ scroll-snap-align: start; @media (max-width: 639px) { + overflow-x: hidden; overflow-y: auto; scroll-snap-align: center; } @@ -198,13 +205,13 @@ /* The wrapper around the cards used to clip overflow while transitioning. * Also, don't resize cards while transitioning to avoid reflow. */ .cards__list { - align-items: flex-end; /* use flex-start to wipe from left */ display: flex; flex-direction: column; gap: var(--cards-gap); margin-block-start: -1ch; margin-inline: -1ch; overflow: hidden; + overflow-y: auto; .cards:not(.is-collapsed) & { padding: 1ch; diff --git a/app/views/boards/columns/_list.html.erb b/app/views/boards/columns/_list.html.erb index 7adedf322..47b33413d 100644 --- a/app/views/boards/columns/_list.html.erb +++ b/app/views/boards/columns/_list.html.erb @@ -1,3 +1,3 @@ -
+
<%= render "cards/display/previews", cards: cards, draggable: draggable %>
From 463b99dd7ec5044e3585965ff215a5db2dbae047 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 15 Dec 2025 15:40:58 -0600 Subject: [PATCH 16/65] Correct padding --- app/assets/stylesheets/card-columns.css | 127 ++++------------------- app/assets/stylesheets/drag_and_drop.css | 4 - 2 files changed, 21 insertions(+), 110 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 48ce031c0..a07f34346 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -36,7 +36,7 @@ --bubble-size: 3.5rem; --cards-gap: min(1.2cqi, 1.7rem); --column-gap: 8px; - --column-padding-expanded: calc(var(--column-gap) * 2); + --column-padding: calc(var(--column-gap) * 2); --column-transition-duration: 300ms; --column-width-collapsed: 40px; --column-width-expanded: 450px; @@ -70,7 +70,6 @@ } @media (max-width: 639px) { - --column-padding-expanded: 0px; --column-width-expanded: calc(100vw - var(--column-gap) * 4); scroll-snap-type: inline mandatory; @@ -118,8 +117,7 @@ scroll-snap-align: start; @media (max-width: 639px) { - overflow-x: hidden; - overflow-y: auto; + overflow: hidden; scroll-snap-align: center; } @@ -182,10 +180,6 @@ 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); @@ -200,6 +194,14 @@ .no-transitions & { transition: none; } + + /* Use flex so the __list container can take up the remaining space for scrolling */ + @media (max-width: 639px) { + .is-expanded & { + display: flex; + flex-direction: column; + } + } } /* The wrapper around the cards used to clip overflow while transitioning. @@ -208,17 +210,16 @@ display: flex; flex-direction: column; gap: var(--cards-gap); - margin-block-start: -1ch; - margin-inline: -1ch; - overflow: hidden; + overflow-x: hidden; overflow-y: auto; - .cards:not(.is-collapsed) & { - padding: 1ch; - } + .is-expanded & { + padding: var(--column-padding); - .card { - inline-size: calc(var(--column-width-expanded) - var(--column-padding-expanded) * 2); + /* Use the rest of the column height for scrolling */ + @media (max-width: 639px) { + flex: 1; + } } .cards--grid & { @@ -294,11 +295,12 @@ block-size: 100%; } - .cards:not(.is-collapsed) & { + .cards.is-expanded & { display: grid; grid-template-areas: "menu expander maximize"; grid-template-columns: var(--column-width-collapsed) 1fr var(--column-width-collapsed); margin-block-end: calc(0.5 * var(--cards-gap)); + padding-inline: var(--column-padding); } } @@ -457,44 +459,6 @@ } } - /* TODO: I think this is legacy now? */ - .cards__heading { - display: flex; - font-size: var(--text-medium); - inset-block-start: 0; - justify-content: center; - margin-block-start: 1px; - margin-inline: -8px; /* enough to cover card shadows, but avoid overlapping bubbles */ - padding-block: var(--cards-gap) 0; - position: sticky; - z-index: 3; - - /* Cover bubbles and cards on small viewports */ - @media (max-width: 639px) { - margin-inline: calc(-1 * var(--cards-gap) - var(--main-padding)); - } - - &, .cards__filter { - font-weight: bold; - text-transform: uppercase; - } - - .cards__filter { - --filter-block-padding: 0.5rem; - - background-color: var(--color-canvas); - border-radius: 99rem; - inline-size: auto; - margin-block: calc(-1 * var(--filter-block-padding)); - padding-block: var(--filter-block-padding); - } - - dialog { - font-weight: normal; - text-transform: none; - } - } - /* Override card styles within columns /* ------------------------------------------------------------------------ */ @@ -629,7 +593,8 @@ --card-padding-block: var(--block-space); border: 1px solid var(--border-color); - margin-block-end: var(--cards-gap); + inline-size: auto; + margin-inline: var(--column-padding); text-align: center; &:has(dialog[open]) { @@ -781,54 +746,4 @@ } } } - - /* Mobile columns - /* -------------------------------------------------------------------------- */ - - .mobile-card-columns { - --column-gap: 8px; - --column-padding-expanded: calc(var(--column-gap) * 2); - --column-width-collapsed: 40px; - --progress-increment: var(--progress-max-width) / var(--progress-max-cards); - --progress-max-cards: 20; - --progress-max-width: 100%; - - padding-inline: 3vw; - - /* Hide on larger devices with cursors */ - @media (min-width: 520px) { - display: none; - } - - .cards.is-collapsed { - inline-size: auto; - display: block; - padding-block: 0.5ch; - - .cards__expander { - --gradient-direction: to right; - - flex-direction: row; - inline-size: auto; - - &:before { - block-size: 1px; - inline-size: 100%; - inset: 50% 0 auto; - translate: 0 -50%; - } - - &:after { - block-size: var(--column-width-collapsed); - inline-size: calc(var(--column-width-collapsed) + var(--card-count) * var(--progress-increment)); - margin-inline-start: 0; - max-inline-size: var(--progress-max-width); - } - } - - .cards__expander-title { - writing-mode: revert; - } - } - } } diff --git a/app/assets/stylesheets/drag_and_drop.css b/app/assets/stylesheets/drag_and_drop.css index 2ada036b4..03943be14 100644 --- a/app/assets/stylesheets/drag_and_drop.css +++ b/app/assets/stylesheets/drag_and_drop.css @@ -15,9 +15,5 @@ outline-offset: -2px; transition: background-color 200ms; z-index: 1; - - .cards__heading { - background-color: transparent; - } } } From ea4520575ab9e5fec83daf26d52cde115697fc8f Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 15 Dec 2025 15:48:40 -0600 Subject: [PATCH 17/65] Hide scrollbars on lists --- app/views/boards/columns/_empty_placeholder.html.erb | 2 +- app/views/boards/columns/_list.html.erb | 2 +- app/views/boards/show/_maybe.html.erb | 2 +- app/views/public/boards/columns/_list.html.erb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/boards/columns/_empty_placeholder.html.erb b/app/views/boards/columns/_empty_placeholder.html.erb index 36507c1ff..51a5b79e6 100644 --- a/app/views/boards/columns/_empty_placeholder.html.erb +++ b/app/views/boards/columns/_empty_placeholder.html.erb @@ -1,4 +1,4 @@ -
+

Drag cards here

diff --git a/app/views/boards/columns/_list.html.erb b/app/views/boards/columns/_list.html.erb index 47b33413d..5fda3012d 100644 --- a/app/views/boards/columns/_list.html.erb +++ b/app/views/boards/columns/_list.html.erb @@ -1,3 +1,3 @@ -
+
<%= render "cards/display/previews", cards: cards, draggable: draggable %>
diff --git a/app/views/boards/show/_maybe.html.erb b/app/views/boards/show/_maybe.html.erb index b060c357d..36145faaa 100644 --- a/app/views/boards/show/_maybe.html.erb +++ b/app/views/boards/show/_maybe.html.erb @@ -13,7 +13,7 @@ <%= render "boards/columns/list", cards: page.records, draggable: true %> <% end %> <% else %> -
+
<% end %> <% end %> diff --git a/app/views/public/boards/columns/_list.html.erb b/app/views/public/boards/columns/_list.html.erb index c73390257..b12af2185 100644 --- a/app/views/public/boards/columns/_list.html.erb +++ b/app/views/public/boards/columns/_list.html.erb @@ -1,3 +1,3 @@ -
+
<%= render "cards/display/public_previews", cards: cards %>
From e24569584d827ac1cf6f065c75ccbe74029748de Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 15 Dec 2025 15:51:53 -0600 Subject: [PATCH 18/65] Move board-tools inside list --- app/assets/stylesheets/card-columns.css | 1 - app/views/boards/columns/_list.html.erb | 1 + app/views/boards/show/_maybe.html.erb | 5 +++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index a07f34346..c5b3c3486 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -594,7 +594,6 @@ border: 1px solid var(--border-color); inline-size: auto; - margin-inline: var(--column-padding); text-align: center; &:has(dialog[open]) { diff --git a/app/views/boards/columns/_list.html.erb b/app/views/boards/columns/_list.html.erb index 5fda3012d..3c7ed5068 100644 --- a/app/views/boards/columns/_list.html.erb +++ b/app/views/boards/columns/_list.html.erb @@ -1,3 +1,4 @@
+ <%= yield %> <%= render "cards/display/previews", cards: cards, draggable: draggable %>
diff --git a/app/views/boards/show/_maybe.html.erb b/app/views/boards/show/_maybe.html.erb index 36145faaa..552cef52c 100644 --- a/app/views/boards/show/_maybe.html.erb +++ b/app/views/boards/show/_maybe.html.erb @@ -7,10 +7,11 @@ <%= render "boards/show/expander", title: "Maybe?", count: board.cards.awaiting_triage.count, column_id: "maybe" %> <%= render "boards/show/menu/maximize", column_path: board_columns_stream_path(board) %>
- <%= render "columns/show/add_card_button", board: board %> <% if page.used? %> <%= with_automatic_pagination "maybe", @page do %> - <%= render "boards/columns/list", cards: page.records, draggable: true %> + <%= render "boards/columns/list", cards: page.records, draggable: true do %> + <%= render "columns/show/add_card_button", board: board %> + <% end %> <% end %> <% else %>
From c7ee0ad040338e1fa4d378cee9df48deaf503536 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 15 Dec 2025 15:53:40 -0600 Subject: [PATCH 19/65] Mini bubble on Maybe column --- app/assets/stylesheets/card-columns.css | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index c5b3c3486..c4ca41c96 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -116,9 +116,11 @@ position: relative; scroll-snap-align: start; - @media (max-width: 639px) { - overflow: hidden; - scroll-snap-align: center; + &.is-expanded { + @media (max-width: 639px) { + overflow: hidden; + scroll-snap-align: center; + } } &.is-collapsed { @@ -678,6 +680,7 @@ /* Surface a mini bubble if there are cards with bubbles inside */ .cards--maybe:has(.bubble:not([hidden])) .cards__expander-title, + .cards--maybe.is-collapsed:has(.bubble:not([hidden])) .cards__transition-container, .cards--doing.is-collapsed:has(.bubble:not([hidden])) .cards__transition-container { --bubble-color: var(--card-color, oklch(var(--lch-blue-medium))); --bubble-shape: 54% 46% 61% 39% / 57% 49% 51% 43%; @@ -698,7 +701,7 @@ } /* Maybe column: position bubble relative to the title, not the container */ - .cards--maybe & { + .cards--maybe.is-expanded & { overflow: visible; position: relative; From 0f1ee03edbd297f18680ffd823bd887bd7ded24d Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 15 Dec 2025 15:55:19 -0600 Subject: [PATCH 20/65] Remove vertical offset on mobile --- app/assets/stylesheets/card-columns.css | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index c4ca41c96..f540f3d8e 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -178,8 +178,10 @@ translate: 0; transition: translate var(--column-transition-duration) var(--ease-out-overshoot-subtle); - .is-collapsed & { - translate: 0 var(--column-width-collapsed); + @media (min-width: 640px) { + .is-collapsed & { + translate: 0 var(--column-width-collapsed); + } } .drag-and-drop__hover-container & { @@ -245,7 +247,9 @@ } .cards__new-column { - margin-block-start: var(--column-width-collapsed); + @media (min-width: 640px) { + margin-block-start: var(--column-width-collapsed); + } } /* Cards grid; used when filtering From a3723ef0d2312b3c9b246d39b48605770f1a4ad1 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 15 Dec 2025 16:03:16 -0600 Subject: [PATCH 21/65] Cleanup and scroll into view when expanding --- app/assets/stylesheets/card-columns.css | 8 ++++---- .../controllers/collapsible_columns_controller.js | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index f540f3d8e..53257fae1 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -411,7 +411,7 @@ } } - .cards:not(.is-collapsed) & { + .cards.is-expanded & { inline-size: 100%; justify-content: center; } @@ -421,7 +421,7 @@ line-height: var(--column-width-collapsed); inline-size: var(--column-width-collapsed); - .cards:not(.is-collapsed) & { + .cards.is-expanded & { display: none; } } @@ -439,7 +439,7 @@ writing-mode: vertical-rl; } - .cards:not(.is-collapsed) & { + .cards.is-expanded & { align-items: center; display: flex; gap: 0.25ch; @@ -458,7 +458,7 @@ display: none; } - .cards:not(.is-collapsed) .cards__expander:hover & { + .cards.is-expanded .cards__expander:hover & { opacity: 0.66; scale: 1; } diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index e9974b0ce..806e70c76 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -110,6 +110,10 @@ export default class extends Controller { column.classList.remove(this.collapsedClass) column.classList.add(this.expandedClass) localStorage.setItem(key, true) + + if (window.matchMedia('(max-width: 639px)').matches) { + column.scrollIntoView({ behavior: "smooth", inline: "nearest" }) + } } #buttonFor(column) { From 9504b986ab8c2d305bdf55c3ba2c8f9b3c1e22cf Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 15 Dec 2025 16:07:51 -0600 Subject: [PATCH 22/65] Tighten up column padding on mobile --- app/assets/stylesheets/card-columns.css | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 53257fae1..0cf8e655a 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -223,6 +223,7 @@ /* Use the rest of the column height for scrolling */ @media (max-width: 639px) { flex: 1; + padding-inline: calc(var(--column-padding) / 4); } } From 623dd90c66f2fb2f72dcda01dc9b62f457911b2b Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 15 Dec 2025 16:12:07 -0600 Subject: [PATCH 23/65] Looser scroll snap --- app/assets/stylesheets/card-columns.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 0cf8e655a..e08b5748d 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -72,7 +72,7 @@ @media (max-width: 639px) { --column-width-expanded: calc(100vw - var(--column-gap) * 4); - scroll-snap-type: inline mandatory; + scroll-snap-type: inline; &:not(:has(.is-expanded)) { grid-template-columns: auto var(--column-width-collapsed) auto; From e23b8711723898c829b4f72367835affa9b74aae Mon Sep 17 00:00:00 2001 From: Italo Matos Date: Tue, 16 Dec 2025 12:25:25 -0300 Subject: [PATCH 24/65] Fix: Maintain search input focus after form submission Problem: When pressing Enter in the search input field, focus was lost after the form was submitted and the Turbo Frame updated with search results. This forced users to click back into the field to continue interacting with the search. Solution: Added a turbo:frame-load event listener in the bar controller's showModalAndSubmit method that automatically restores focus to the search input after the Turbo Frame finishes loading the results. The listener uses {once: true} to auto-cleanup after execution. --- app/javascript/controllers/bar_controller.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/javascript/controllers/bar_controller.js b/app/javascript/controllers/bar_controller.js index 5e72e4e07..522e3eee3 100644 --- a/app/javascript/controllers/bar_controller.js +++ b/app/javascript/controllers/bar_controller.js @@ -34,6 +34,11 @@ export default class extends Controller { showModalAndSubmit(event) { this.showModal() this.formTarget.requestSubmit() + + // Restore focus to search input after turbo frame loads + this.turboFrameTarget.addEventListener("turbo:frame-load", () => { + this.searchInputTarget.focus() + }, { once: true }) } showModal() { From d66e2f0b8ac9991bc45c96ecbcaf35614423cb7e Mon Sep 17 00:00:00 2001 From: Italo Matos Date: Fri, 19 Dec 2025 12:33:00 -0300 Subject: [PATCH 25/65] remove comment and move logic to method --- app/javascript/controllers/bar_controller.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/javascript/controllers/bar_controller.js b/app/javascript/controllers/bar_controller.js index 522e3eee3..e83bce9a9 100644 --- a/app/javascript/controllers/bar_controller.js +++ b/app/javascript/controllers/bar_controller.js @@ -34,11 +34,7 @@ export default class extends Controller { showModalAndSubmit(event) { this.showModal() this.formTarget.requestSubmit() - - // Restore focus to search input after turbo frame loads - this.turboFrameTarget.addEventListener("turbo:frame-load", () => { - this.searchInputTarget.focus() - }, { once: true }) + this.#restoreFocusAfterTurboFrameLoads() } showModal() { @@ -56,6 +52,12 @@ export default class extends Controller { } } + #restoreFocusAfterTurboFrameLoads() { + this.turboFrameTarget.addEventListener("turbo:frame-load", () => { + this.searchInputTarget.focus() + }, { once: true }) + } + #loadTurboFrame() { this.turboFrameTarget.src = this.searchUrlValue } From b7951718f3e88eca79886df384a3265e9f039647 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 19 Dec 2025 15:27:18 -0600 Subject: [PATCH 26/65] Position New Column absolutely to center collapsed columns --- app/assets/stylesheets/card-columns.css | 20 ++++++++++++-------- app/views/boards/show/menu/_columns.html.erb | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 38f567444..b0710a81f 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -72,7 +72,7 @@ @media (max-width: 639px) { --column-width-expanded: calc(100vw - var(--column-gap) * 4); - scroll-snap-type: inline; + scroll-snap-type: inline mandatory; &:not(:has(.is-expanded)) { grid-template-columns: auto var(--column-width-collapsed) auto; @@ -89,10 +89,12 @@ align-items: stretch; display: flex; gap: var(--column-gap); + position: relative; } .card-columns__left { justify-content: end; + margin-inline-start: auto; padding-inline-start: var(--column-gap); @media (max-width: 639px) { @@ -103,6 +105,7 @@ .card-columns__right { justify-content: start; padding-inline-end: var(--column-gap); + margin-inline-end: auto; } /* Column @@ -248,6 +251,14 @@ } .cards__new-column { + position: relative; + + @media (max-width: 639px) { + inset-inline-end: 0; + position: absolute; + translate: 100%; + } + @media (min-width: 640px) { margin-block-start: var(--column-width-collapsed); } @@ -689,13 +700,6 @@ } } - /* Closed (Done) - /* ------------------------------------------------------------------------ */ - - .cards--closed { - - } - /* Doing /* -------------------------------------------------------------------------- */ diff --git a/app/views/boards/show/menu/_columns.html.erb b/app/views/boards/show/menu/_columns.html.erb index 70aac7c90..017527a43 100644 --- a/app/views/boards/show/menu/_columns.html.erb +++ b/app/views/boards/show/menu/_columns.html.erb @@ -1,4 +1,4 @@ -
+
- <% if page.used? %> - <%= with_automatic_pagination "maybe", @page do %> - <%= render "boards/columns/list", cards: page.records, draggable: true do %> - <%= render "columns/show/add_card_button", board: board %> +
+ <%= render "columns/show/add_card_button", board: board %> +
+
+ <% if page.used? %> + <%= with_automatic_pagination "maybe", @page do %> + <%= render "cards/display/previews", cards: page.records, draggable: true %> <% end %> <% end %> - <% else %> -
-
- <% end %> +
<% end %> diff --git a/app/views/public/boards/columns/_list.html.erb b/app/views/public/boards/columns/_list.html.erb deleted file mode 100644 index b12af2185..000000000 --- a/app/views/public/boards/columns/_list.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -
- <%= render "cards/display/public_previews", cards: cards %> -
diff --git a/app/views/public/boards/columns/closeds/show.html.erb b/app/views/public/boards/columns/closeds/show.html.erb index 0986b06fc..21977743e 100644 --- a/app/views/public/boards/columns/closeds/show.html.erb +++ b/app/views/public/boards/columns/closeds/show.html.erb @@ -12,13 +12,15 @@
<%= turbo_frame_tag :closed_column do %> - <% if @page.used? %> - <%= with_automatic_pagination :closed_column, @page do %> - <%= render "public/boards/columns/list", cards: @page.records %> +
+ <% if @page.used? %> + <%= with_automatic_pagination :closed_column, @page do %> + <%= render "cards/display/public_previews", cards: @page.records %> + <% end %> + <% else %> +
No cards here
<% end %> - <% else %> -
No cards here
- <% end %> +
<% end %>
diff --git a/app/views/public/boards/columns/not_nows/show.html.erb b/app/views/public/boards/columns/not_nows/show.html.erb index c92c6bce4..786cb7afb 100644 --- a/app/views/public/boards/columns/not_nows/show.html.erb +++ b/app/views/public/boards/columns/not_nows/show.html.erb @@ -12,13 +12,15 @@
<%= turbo_frame_tag :not_now_column do %> - <% if @page.used? %> - <%= with_automatic_pagination :not_now_column, @page do %> - <%= render "public/boards/columns/list", cards: @page.records %> +
+ <% if @page.used? %> + <%= with_automatic_pagination :not_now_column, @page do %> + <%= render "cards/display/public_previews", cards: @page.records %> + <% end %> + <% else %> +
No cards here
<% end %> - <% else %> -
No cards here
- <% end %> +
<% end %>
diff --git a/app/views/public/boards/columns/show.html.erb b/app/views/public/boards/columns/show.html.erb index 68d3b168d..6f0d2542f 100644 --- a/app/views/public/boards/columns/show.html.erb +++ b/app/views/public/boards/columns/show.html.erb @@ -12,13 +12,15 @@
<%= turbo_frame_tag @column, :cards do %> - <% if @page.used? %> - <%= with_automatic_pagination dom_id(@column, :cards), @page do %> - <%= render "public/boards/columns/list", cards: @page.records %> +
+ <% if @page.used? %> + <%= with_automatic_pagination dom_id(@column, :cards), @page do %> + <%= render "cards/display/public_previews", cards: @page.records %> + <% end %> + <% else %> +
No cards here
<% end %> - <% else %> -
No cards here
- <% end %> +
<% end %>
diff --git a/app/views/public/boards/columns/streams/show.html.erb b/app/views/public/boards/columns/streams/show.html.erb index 77902570a..4df8c2b12 100644 --- a/app/views/public/boards/columns/streams/show.html.erb +++ b/app/views/public/boards/columns/streams/show.html.erb @@ -12,13 +12,15 @@
<%= turbo_frame_tag :stream_column do %> - <% if @page.used? %> - <%= with_automatic_pagination :stream_column, @page do %> - <%= render "public/boards/columns/list", cards: @page.records %> +
+ <% if @page.used? %> + <%= with_automatic_pagination :stream_column, @page do %> + <%= render "cards/display/public_previews", cards: @page.records %> + <% end %> + <% else %> +
No cards here
<% end %> - <% else %> -
No cards here
- <% end %> +
<% end %>
From d3bfd14c228ea13bdf046e21b4cce9bc15796e0e Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 2 Jan 2026 12:11:17 -0600 Subject: [PATCH 41/65] Only auto-focus Maybe column on desktop --- .../controllers/collapsible_columns_controller.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index 806e70c76..a2e337df2 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -48,7 +48,7 @@ export default class extends Controller { } focusOnColumn({ target }) { - if (this.#isCollapsed(target)) { + if (this.#isDesktop && this.#isCollapsed(target)) { this.#collapseAllExcept(target) this.#expand(target) } @@ -97,7 +97,7 @@ export default class extends Controller { #collapse(column) { const key = this.#localStorageKeyFor(column) - this.#buttonFor(column).setAttribute("aria-expanded", "false") + this.#buttonFor(column)?.setAttribute("aria-expanded", "false") column.classList.remove(this.expandedClass) column.classList.add(this.collapsedClass) localStorage.removeItem(key) @@ -106,7 +106,7 @@ export default class extends Controller { #expand(column) { const key = this.#localStorageKeyFor(column) - this.#buttonFor(column).setAttribute("aria-expanded", "true") + this.#buttonFor(column)?.setAttribute("aria-expanded", "true") column.classList.remove(this.collapsedClass) column.classList.add(this.expandedClass) localStorage.setItem(key, true) @@ -170,8 +170,10 @@ export default class extends Controller { } #handleMobileMode() { - this.columnTargets.forEach(column => this.#collapse(column)) this.#maybeButton.removeAttribute("disabled") + this.columnTargets.forEach(column => { + this.#collapse(column) + }) } get #maybeButton() { From 837b8202918ac065b67e40ce7273087efad93919 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 2 Jan 2026 12:22:07 -0600 Subject: [PATCH 42/65] Better scroll snapping --- app/assets/stylesheets/card-columns.css | 3 +-- app/javascript/controllers/collapsible_columns_controller.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 316979296..46d2cb498 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -117,12 +117,11 @@ inline-size: var(--column-width-expanded); outline: none; position: relative; - scroll-snap-align: start; + scroll-snap-align: center; &.is-expanded { @media (max-width: 639px) { overflow: hidden; - scroll-snap-align: center; } } diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index a2e337df2..372d0ae14 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -112,7 +112,7 @@ export default class extends Controller { localStorage.setItem(key, true) if (window.matchMedia('(max-width: 639px)').matches) { - column.scrollIntoView({ behavior: "smooth", inline: "nearest" }) + column.scrollIntoView({ behavior: "smooth", inline: "center" }) } } From 5b6fdbfeae402c90eb62e5bb69b6215dd3ecfe6a Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 2 Jan 2026 12:45:53 -0600 Subject: [PATCH 43/65] Fix mini bubble cropping, filters padding, and grid-style --- app/assets/stylesheets/card-columns.css | 17 ++++++++++++----- app/assets/stylesheets/filters.css | 4 ++-- app/helpers/columns_helper.rb | 2 +- app/views/boards/show/_filtered_cards.html.erb | 10 ++++++---- app/views/boards/show/_maybe.html.erb | 4 +--- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 46d2cb498..14b7e3a5e 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -177,7 +177,7 @@ .cards__transition-container { block-size: 100%; border-radius: calc(var(--column-width-collapsed) / 2); - translate: 0; + translate: 0 0.5ch; // Allow a little room for the mini bubble */ transition: translate var(--column-transition-duration) var(--ease-out-overshoot-subtle); @media (min-width: 640px) { @@ -229,10 +229,6 @@ } } - .cards--grid & { - display: contents; - } - [aria-selected] & .card[aria-selected] { outline: var(--focus-ring-size) solid var(--color-selected-dark); outline-offset: var(--focus-ring-offset); @@ -270,14 +266,17 @@ --cards-gap: 1rem; --card-grid-columns: 1; + align-items: start; container-type: inline-size; display: flex; flex-direction: row; flex-wrap: wrap; gap: var(--cards-gap); justify-content: center; + inline-size: 100%; margin-inline: auto; max-inline-size: var(--main-width); + padding-inline: 1ch; @media (min-width: 640px) { --card-grid-columns: 2; @@ -287,6 +286,10 @@ --card-grid-columns: 3; } + .cards__list { + display: contents; + } + .card { inline-size: calc((100% - var(--cards-gap) * (var(--card-grid-columns) - 1) ) / var(--card-grid-columns)); } @@ -633,6 +636,10 @@ inline-size: auto; text-align: center; + @media (min-width: 800px) { + margin-inline: var(--column-padding); + } + &:has(dialog[open]) { z-index: 5; } diff --git a/app/assets/stylesheets/filters.css b/app/assets/stylesheets/filters.css index b283fa447..800e1b31b 100644 --- a/app/assets/stylesheets/filters.css +++ b/app/assets/stylesheets/filters.css @@ -7,11 +7,11 @@ align-items: center; display: flex; flex-wrap: wrap; - gap: 1ch; gap: var(--inline-space-half); justify-content: center; + padding-block-start: 2px; /* prevents input focus-ring clipping on mobile */ position: relative; - view-transition-name: "filters"; + view-transition-name: filters; .btn { --btn-border-color: var(--color-ink-medium); diff --git a/app/helpers/columns_helper.rb b/app/helpers/columns_helper.rb index b14e6adc6..6b9459c52 100644 --- a/app/helpers/columns_helper.rb +++ b/app/helpers/columns_helper.rb @@ -11,7 +11,7 @@ module ColumnsHelper end def column_tag(id:, name:, drop_url:, collapsed: true, selected: nil, card_color: "var(--color-card-default)", data: {}, **properties, &block) - classes = token_list("cards", properties.delete(:class), "is-collapsed": collapsed) + classes = token_list("cards", properties.delete(:class), "is-collapsed": collapsed, "is-expanded": !collapsed) hotkeys_disabled = data[:card_hotkeys_disabled] data = { diff --git a/app/views/boards/show/_filtered_cards.html.erb b/app/views/boards/show/_filtered_cards.html.erb index 5cfcd39b7..4e4d8f749 100644 --- a/app/views/boards/show/_filtered_cards.html.erb +++ b/app/views/boards/show/_filtered_cards.html.erb @@ -1,6 +1,8 @@
- <%= with_automatic_pagination :filtered_cards_paginated_container, page do %> - <%= render "cards/display/previews", cards: page.records, draggable: true %> - <% end %> -
No cards match this filter
+
+ <%= with_automatic_pagination :filtered_cards_paginated_container, page do %> + <%= render "cards/display/previews", cards: page.records, draggable: true %> + <% end %> +
No cards match this filter
+
diff --git a/app/views/boards/show/_maybe.html.erb b/app/views/boards/show/_maybe.html.erb index 54f151af9..7773fb292 100644 --- a/app/views/boards/show/_maybe.html.erb +++ b/app/views/boards/show/_maybe.html.erb @@ -7,9 +7,7 @@ <%= render "boards/show/expander", title: "Maybe?", count: board.cards.awaiting_triage.count, column_id: "maybe" %> <%= render "boards/show/menu/maximize", column_path: board_columns_stream_path(board) %> -
- <%= render "columns/show/add_card_button", board: board %> -
+ <%= render "columns/show/add_card_button", board: board %>
<% if page.used? %> <%= with_automatic_pagination "maybe", @page do %> From 5f38e8957ef849b75b3a365ee9e9e4ea9663c68d Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Mon, 5 Jan 2026 15:58:29 +0100 Subject: [PATCH 44/65] Reapply "Make sure new card drafts are refreshed when reused" This reverts commit 045ddf78f3517f9b983b2c68e4baf0088750b448. --- app/controllers/cards_controller.rb | 2 +- app/models/user/accessor.rb | 6 ++++++ test/models/user/accessor_test.rb | 32 +++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/app/controllers/cards_controller.rb b/app/controllers/cards_controller.rb index daf22a5bc..4b7b4748f 100644 --- a/app/controllers/cards_controller.rb +++ b/app/controllers/cards_controller.rb @@ -12,7 +12,7 @@ class CardsController < ApplicationController def create respond_to do |format| format.html do - card = @board.cards.find_or_create_by!(creator: Current.user, status: "drafted") + card = Current.user.draft_new_card_at(@board) redirect_to card end diff --git a/app/models/user/accessor.rb b/app/models/user/accessor.rb index a9cc5e459..b9744a667 100644 --- a/app/models/user/accessor.rb +++ b/app/models/user/accessor.rb @@ -11,6 +11,12 @@ module User::Accessor after_create_commit :grant_access_to_boards, unless: :system? end + def draft_new_card_at(board) + board.cards.find_or_initialize_by(creator: self, status: "drafted").tap do |card| + card.update!(created_at: Time.current, updated_at: Time.current, last_active_at: Time.current) + end + end + private def grant_access_to_boards Access.insert_all account.boards.all_access.pluck(:id).collect { |board_id| { id: ActiveRecord::Type::Uuid.generate, board_id: board_id, user_id: id, account_id: account.id } } diff --git a/test/models/user/accessor_test.rb b/test/models/user/accessor_test.rb index 95cb84edb..300c2c741 100644 --- a/test/models/user/accessor_test.rb +++ b/test/models/user/accessor_test.rb @@ -12,4 +12,36 @@ class User::AccessorTest < ActiveSupport::TestCase system_user = User.create!(account: accounts("37s"), role: "system", name: "Test System User") assert_empty system_user.boards end + + test "creating a new card draft sets current timestamps" do + user = users(:david) + board = boards(:writebook) + + freeze_time do + card = user.draft_new_card_at(board) + + assert card.persisted? + assert card.drafted? + assert_equal user, card.creator + assert_equal board, card.board + assert_equal Time.current, card.created_at + assert_equal Time.current, card.updated_at + assert_equal Time.current, card.last_active_at + end + end + + test "reusing an existing card draft refreshes timestamps" do + existing_draft = cards(:unfinished_thoughts) + user = existing_draft.creator + board = existing_draft.board + + freeze_time do + card = user.draft_new_card_at(board) + + assert_equal existing_draft, card + assert_equal Time.current, card.created_at + assert_equal Time.current, card.updated_at + assert_equal Time.current, card.last_active_at + end + end end From 85bfa7aa4882ab1583d06f7a2018dee6cb4740f1 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Mon, 5 Jan 2026 16:01:11 +0100 Subject: [PATCH 45/65] Rename method --- app/controllers/cards_controller.rb | 2 +- app/models/user/accessor.rb | 2 +- test/models/user/accessor_test.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/cards_controller.rb b/app/controllers/cards_controller.rb index 4b7b4748f..e79eeb22b 100644 --- a/app/controllers/cards_controller.rb +++ b/app/controllers/cards_controller.rb @@ -12,7 +12,7 @@ class CardsController < ApplicationController def create respond_to do |format| format.html do - card = Current.user.draft_new_card_at(@board) + card = Current.user.draft_new_card_in(@board) redirect_to card end diff --git a/app/models/user/accessor.rb b/app/models/user/accessor.rb index b9744a667..5dc1cdd90 100644 --- a/app/models/user/accessor.rb +++ b/app/models/user/accessor.rb @@ -11,7 +11,7 @@ module User::Accessor after_create_commit :grant_access_to_boards, unless: :system? end - def draft_new_card_at(board) + def draft_new_card_in(board) board.cards.find_or_initialize_by(creator: self, status: "drafted").tap do |card| card.update!(created_at: Time.current, updated_at: Time.current, last_active_at: Time.current) end diff --git a/test/models/user/accessor_test.rb b/test/models/user/accessor_test.rb index 300c2c741..0ff1074a2 100644 --- a/test/models/user/accessor_test.rb +++ b/test/models/user/accessor_test.rb @@ -18,7 +18,7 @@ class User::AccessorTest < ActiveSupport::TestCase board = boards(:writebook) freeze_time do - card = user.draft_new_card_at(board) + card = user.draft_new_card_in(board) assert card.persisted? assert card.drafted? @@ -36,7 +36,7 @@ class User::AccessorTest < ActiveSupport::TestCase board = existing_draft.board freeze_time do - card = user.draft_new_card_at(board) + card = user.draft_new_card_in(board) assert_equal existing_draft, card assert_equal Time.current, card.created_at From c8c6fbbb1315dbb86bb471e48398b338e5598c08 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 5 Jan 2026 12:49:38 -0600 Subject: [PATCH 46/65] Remember expanded state on navigation --- app/assets/stylesheets/card-columns.css | 6 ++--- .../collapsible_columns_controller.js | 24 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 09f93de0c..4f1b885b9 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -320,7 +320,6 @@ display: grid; grid-template-areas: "menu expander maximize"; grid-template-columns: var(--column-width-collapsed) 1fr var(--column-width-collapsed); - margin-block-end: calc(0.5 * var(--cards-gap)); padding-inline: var(--column-padding); } } @@ -364,6 +363,7 @@ font-weight: 600; gap: 0.5ch; grid-area: expander; + outline: none; outline-offset: -2px; position: relative; text-transform: uppercase; @@ -414,7 +414,7 @@ block-size: 100%; content: ""; inline-size: 1px; - inset: 0 auto; + inset-block: calc(var(--column-width-collapsed) + var(--card-count) * var(--progress-increment)) 0; position: absolute; z-index: -2; } @@ -639,7 +639,7 @@ text-align: center; @media (min-width: 800px) { - margin-inline: var(--column-padding); + margin: var(--column-padding) var(--column-padding) 0; } &:has(dialog[open]) { diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index 372d0ae14..1a9ba4174 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -103,13 +103,15 @@ export default class extends Controller { localStorage.removeItem(key) } - #expand(column) { - const key = this.#localStorageKeyFor(column) - + #expand(column, saveState = true) { this.#buttonFor(column)?.setAttribute("aria-expanded", "true") column.classList.remove(this.collapsedClass) column.classList.add(this.expandedClass) - localStorage.setItem(key, true) + + if (saveState) { + const key = this.#localStorageKeyFor(column) + localStorage.setItem(key, true) + } if (window.matchMedia('(max-width: 639px)').matches) { column.scrollIntoView({ behavior: "smooth", inline: "center" }) @@ -129,6 +131,7 @@ export default class extends Controller { #restoreColumn(column) { const key = this.#localStorageKeyFor(column) if (localStorage.getItem(key)) { + this.#collapseAllExcept(column) this.#expand(column) } } @@ -165,15 +168,20 @@ export default class extends Controller { } async #handleDesktopMode() { - this.#expand(this.maybeColumnTarget) + this.#expand(this.maybeColumnTarget, false) this.#maybeButton.setAttribute("disabled", true) } #handleMobileMode() { this.#maybeButton.removeAttribute("disabled") - this.columnTargets.forEach(column => { - this.#collapse(column) - }) + + const expandedColumn = this.columnTargets.find(column => column !== this.maybeColumnTarget && !this.#isCollapsed(column)) + + if (expandedColumn) { + this.#collapseAllExcept(expandedColumn) + } else { + this.#collapseAllExcept(this.maybeColumnTarget) + } } get #maybeButton() { From a9666225e34fde6f90c511e117c912f929473027 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 5 Jan 2026 13:21:00 -0600 Subject: [PATCH 47/65] Fix scrolling on mobile --- app/assets/stylesheets/card-columns.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 4f1b885b9..1aae81535 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -91,6 +91,10 @@ display: flex; gap: var(--column-gap); position: relative; + + @media (max-width: 639px) { + min-block-size: 0; + } } .card-columns__left { From 2a4fbecbdacbdd59ba6f0c6added91971991cee7 Mon Sep 17 00:00:00 2001 From: LU Date: Tue, 6 Jan 2026 17:22:42 +0900 Subject: [PATCH 48/65] Add CJK (Chinese, Japanese, Korean) search support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The search functionality was silently dropping CJK characters because: 1. Query sanitization used `\w` which only matches ASCII word characters 2. Stemmer split by whitespace, which doesn't work for CJK languages 3. Highlighter used `\b` word boundaries that don't apply to CJK This commit fixes all three issues: - Query: Use `\p{L}\p{N}` (Unicode letters/numbers) instead of `\w` - Stemmer: Preserve CJK characters as-is without stemming, since CJK languages don't have stemming rules like English - Highlighter: Skip word boundary matching for CJK terms Also extracts `CJK_PATTERN` to `Search` module to avoid duplication. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/models/search.rb | 2 + app/models/search/highlighter.rb | 10 ++++- app/models/search/query.rb | 2 +- app/models/search/stemmer.rb | 46 ++++++++++++++++++++- test/models/search/highlighter_test.rb | 28 +++++++++++++ test/models/search/query_test.rb | 57 ++++++++++++++++++++++++++ test/models/search/stemmer_test.rb | 36 ++++++++++++++++ 7 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 test/models/search/query_test.rb diff --git a/app/models/search.rb b/app/models/search.rb index f81b9a9d8..3b8127ef1 100644 --- a/app/models/search.rb +++ b/app/models/search.rb @@ -1,4 +1,6 @@ module Search + CJK_PATTERN = /\p{Han}|\p{Hiragana}|\p{Katakana}|\p{Hangul}/ + def self.table_name_prefix "search_" end diff --git a/app/models/search/highlighter.rb b/app/models/search/highlighter.rb index d2b53d772..8672ed3c8 100644 --- a/app/models/search/highlighter.rb +++ b/app/models/search/highlighter.rb @@ -13,8 +13,14 @@ class Search::Highlighter result = text.dup terms.each do |term| - result.gsub!(/\b(#{Regexp.escape(term)}\w*)\b/i) do |match| - "#{OPENING_MARK}#{match}#{CLOSING_MARK}" + if term.match?(Search::CJK_PATTERN) + result.gsub!(/(#{Regexp.escape(term)})/i) do |match| + "#{OPENING_MARK}#{match}#{CLOSING_MARK}" + end + else + result.gsub!(/\b(#{Regexp.escape(term)}\w*)\b/i) do |match| + "#{OPENING_MARK}#{match}#{CLOSING_MARK}" + end end end diff --git a/app/models/search/query.rb b/app/models/search/query.rb index 5fe0829e4..6bf06ef42 100644 --- a/app/models/search/query.rb +++ b/app/models/search/query.rb @@ -33,7 +33,7 @@ class Search::Query < ApplicationRecord end def remove_invalid_search_characters(terms) - terms.gsub(/[^\w"]/, " ") + terms.gsub(/[^\p{L}\p{N}_"]/, " ") end def remove_unbalanced_quotes(terms) diff --git a/app/models/search/stemmer.rb b/app/models/search/stemmer.rb index f6a6c56d4..e19413a8c 100644 --- a/app/models/search/stemmer.rb +++ b/app/models/search/stemmer.rb @@ -5,9 +5,53 @@ module Search::Stemmer def stem(value) if value.present? - value.gsub(/[^\w\s]/, "").split(/\s+/).map { |word| STEMMER.stem(word.downcase) }.join(" ") + tokenize(value).join(" ") else value end end + + private + def tokenize(value) + tokens = [] + current_word = +"" + current_cjk = +"" + + value.each_char do |char| + if cjk_character?(char) + if current_word.present? + tokens << stem_word(current_word) + current_word = +"" + end + current_cjk << char + elsif char =~ /[\p{L}\p{N}_]/ + if current_cjk.present? + tokens << current_cjk + current_cjk = +"" + end + current_word << char + else + if current_word.present? + tokens << stem_word(current_word) + current_word = +"" + end + if current_cjk.present? + tokens << current_cjk + current_cjk = +"" + end + end + end + + tokens << stem_word(current_word) if current_word.present? + tokens << current_cjk if current_cjk.present? + tokens + end + + def cjk_character?(char) + char.match?(Search::CJK_PATTERN) + end + + def stem_word(word) + STEMMER.stem(word.downcase) + end end diff --git a/test/models/search/highlighter_test.rb b/test/models/search/highlighter_test.rb index a065c4da1..822e4913d 100644 --- a/test/models/search/highlighter_test.rb +++ b/test/models/search/highlighter_test.rb @@ -82,6 +82,34 @@ class Search::HighlighterTest < ActiveSupport::TestCase assert_equal "<script>#{mark('test')}</script>", result end + test "highlight Chinese characters" do + highlighter = Search::Highlighter.new("测试") + result = highlighter.highlight("这是一个测试文本") + + assert_equal "这是一个#{mark('测试')}文本", result + end + + test "highlight Japanese characters" do + highlighter = Search::Highlighter.new("テスト") + result = highlighter.highlight("これはテストです") + + assert_equal "これは#{mark('テスト')}です", result + end + + test "highlight Korean characters" do + highlighter = Search::Highlighter.new("테스트") + result = highlighter.highlight("이것은 테스트입니다") + + assert_equal "이것은 #{mark('테스트')}입니다", result + end + + test "highlight mixed CJK and English" do + highlighter = Search::Highlighter.new("world 世界") + result = highlighter.highlight("hello world 你好世界") + + assert_equal "hello #{mark('world')} 你好#{mark('世界')}", result + end + private def mark(text) "#{Search::Highlighter::OPENING_MARK}#{text}#{Search::Highlighter::CLOSING_MARK}" diff --git a/test/models/search/query_test.rb b/test/models/search/query_test.rb new file mode 100644 index 000000000..628b9e69e --- /dev/null +++ b/test/models/search/query_test.rb @@ -0,0 +1,57 @@ +require "test_helper" + +class Search::QueryTest < ActiveSupport::TestCase + setup do + @account = accounts(:"37s") + Current.account = @account + end + + test "sanitize preserves ASCII words" do + query = build_query("hello world") + + assert_equal "hello world", query.terms + end + + test "sanitize preserves Chinese characters" do + query = build_query("测试文本") + + assert_equal "测试文本", query.terms + end + + test "sanitize preserves Japanese characters" do + query = build_query("テスト") + + assert_equal "テスト", query.terms + end + + test "sanitize preserves Korean characters" do + query = build_query("테스트") + + assert_equal "테스트", query.terms + end + + test "sanitize preserves mixed CJK and English" do + query = build_query("hello 世界 test") + + assert_equal "hello 世界 test", query.terms + end + + test "sanitize removes special characters but preserves CJK" do + query = build_query("测试@文本") + + assert_equal "测试 文本", query.terms + end + + test "sanitize preserves quoted phrases with CJK" do + query = build_query('"你好世界"') + + assert_equal '"你好世界"', query.terms + end + + private + def build_query(terms) + query = Search::Query.wrap(terms) + query.validate + query + end +end diff --git a/test/models/search/stemmer_test.rb b/test/models/search/stemmer_test.rb index 858ed2dca..3974b73fc 100644 --- a/test/models/search/stemmer_test.rb +++ b/test/models/search/stemmer_test.rb @@ -12,4 +12,40 @@ class Search::StemmerTest < ActiveSupport::TestCase assert_equal "test run jump walk", result end + + test "preserve Chinese characters" do + result = Search::Stemmer.stem("测试") + + assert_equal "测试", result + end + + test "preserve Japanese characters" do + result = Search::Stemmer.stem("テスト") + + assert_equal "テスト", result + end + + test "preserve Korean characters" do + result = Search::Stemmer.stem("테스트") + + assert_equal "테스트", result + end + + test "mixed CJK and English" do + result = Search::Stemmer.stem("running 测试 test") + + assert_equal "run 测试 test", result + end + + test "mixed CJK and English without spaces" do + result = Search::Stemmer.stem("hello世界test") + + assert_equal "hello 世界 test", result + end + + test "CJK punctuation is treated as separator" do + result = Search::Stemmer.stem("你好。世界") + + assert_equal "你好 世界", result + end end From 366588e26358d0c8e3669344ab34c3728dd3c7b3 Mon Sep 17 00:00:00 2001 From: LU Date: Tue, 6 Jan 2026 17:51:20 +0900 Subject: [PATCH 49/65] Add SQLite FTS5 support for CJK search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Split each CJK character into individual tokens for FTS5 indexing - Add stem_content callback to SQLite adapter to pre-process content - Stem search queries before matching against FTS5 index - Update stemmer tests to reflect character-splitting behavior This enables CJK search on SQLite installations where the FTS5 tokenizer cannot natively segment CJK text. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- app/models/search/record/sqlite.rb | 8 +++++++- app/models/search/stemmer.rb | 12 +----------- test/models/search/stemmer_test.rb | 18 +++++++++--------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/app/models/search/record/sqlite.rb b/app/models/search/record/sqlite.rb index ae0d34281..73b3b675f 100644 --- a/app/models/search/record/sqlite.rb +++ b/app/models/search/record/sqlite.rb @@ -8,9 +8,10 @@ module Search::Record::SQLite has_one :search_records_fts, -> { with_rowid }, class_name: "Search::Record::SQLite::Fts", foreign_key: :rowid, primary_key: :id, dependent: :destroy + before_save :stem_content after_save :upsert_to_fts5_table - scope :matching, ->(query, account_id) { joins(:search_records_fts).where("search_records_fts MATCH ?", query) } + scope :matching, ->(query, account_id) { joins(:search_records_fts).where("search_records_fts MATCH ?", Search::Stemmer.stem(query.to_s)) } end class_methods do @@ -42,6 +43,11 @@ module Search::Record::SQLite end private + def stem_content + self.title = Search::Stemmer.stem(title) if title_changed? + self.content = Search::Stemmer.stem(content) if content_changed? + end + def escape_fts_highlight(html) return nil unless html.present? diff --git a/app/models/search/stemmer.rb b/app/models/search/stemmer.rb index e19413a8c..2ae0e1bf1 100644 --- a/app/models/search/stemmer.rb +++ b/app/models/search/stemmer.rb @@ -15,7 +15,6 @@ module Search::Stemmer def tokenize(value) tokens = [] current_word = +"" - current_cjk = +"" value.each_char do |char| if cjk_character?(char) @@ -23,27 +22,18 @@ module Search::Stemmer tokens << stem_word(current_word) current_word = +"" end - current_cjk << char + tokens << char elsif char =~ /[\p{L}\p{N}_]/ - if current_cjk.present? - tokens << current_cjk - current_cjk = +"" - end current_word << char else if current_word.present? tokens << stem_word(current_word) current_word = +"" end - if current_cjk.present? - tokens << current_cjk - current_cjk = +"" - end end end tokens << stem_word(current_word) if current_word.present? - tokens << current_cjk if current_cjk.present? tokens end diff --git a/test/models/search/stemmer_test.rb b/test/models/search/stemmer_test.rb index 3974b73fc..e2963ee10 100644 --- a/test/models/search/stemmer_test.rb +++ b/test/models/search/stemmer_test.rb @@ -13,39 +13,39 @@ class Search::StemmerTest < ActiveSupport::TestCase assert_equal "test run jump walk", result end - test "preserve Chinese characters" do + test "split Chinese characters for FTS indexing" do result = Search::Stemmer.stem("测试") - assert_equal "测试", result + assert_equal "测 试", result end - test "preserve Japanese characters" do + test "split Japanese characters for FTS indexing" do result = Search::Stemmer.stem("テスト") - assert_equal "テスト", result + assert_equal "テ ス ト", result end - test "preserve Korean characters" do + test "split Korean characters for FTS indexing" do result = Search::Stemmer.stem("테스트") - assert_equal "테스트", result + assert_equal "테 스 트", result end test "mixed CJK and English" do result = Search::Stemmer.stem("running 测试 test") - assert_equal "run 测试 test", result + assert_equal "run 测 试 test", result end test "mixed CJK and English without spaces" do result = Search::Stemmer.stem("hello世界test") - assert_equal "hello 世界 test", result + assert_equal "hello 世 界 test", result end test "CJK punctuation is treated as separator" do result = Search::Stemmer.stem("你好。世界") - assert_equal "你好 世界", result + assert_equal "你 好 世 界", result end end From a966308d775f2e3237e7b492c2a5dbf83fcff2fc Mon Sep 17 00:00:00 2001 From: Dorin Vancea Date: Tue, 6 Jan 2026 16:15:47 +0000 Subject: [PATCH 50/65] Fix Chinese/Japanese characters missing from printed PDFs on macOS --- app/assets/stylesheets/print.css | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/print.css b/app/assets/stylesheets/print.css index c79493458..8e1359e1b 100644 --- a/app/assets/stylesheets/print.css +++ b/app/assets/stylesheets/print.css @@ -7,6 +7,7 @@ --color-canvas: white; --border-dark: 1px solid var(--color-ink); --border-light: 1px solid color-mix(in oklch, var(--color-ink), transparent 75%); + --font-sans: "Adwaita Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Hiragino Sans GB", "Hiragino Sans", "Apple SD Gothic Neo", "Microsoft YaHei", "Meiryo", "Malgun Gothic"; } @page { From 802ccef9bfc2c986e6dc9004af96aec44691d2fc Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Tue, 6 Jan 2026 13:38:50 -0600 Subject: [PATCH 51/65] Fix card grid layout --- app/assets/stylesheets/card-columns.css | 15 +++++++-------- app/views/boards/show/_columns.html.erb | 2 +- .../show/{_maybe.html.erb => _stream.html.erb} | 0 .../cards/drops/streams/create.turbo_stream.erb | 2 +- app/views/public/boards/show/_columns.html.erb | 2 +- 5 files changed, 10 insertions(+), 11 deletions(-) rename app/views/boards/show/{_maybe.html.erb => _stream.html.erb} (100%) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 1aae81535..4f3f9de4a 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -271,17 +271,10 @@ --cards-gap: 1rem; --card-grid-columns: 1; - align-items: start; container-type: inline-size; - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: var(--cards-gap); - justify-content: center; inline-size: 100%; margin-inline: auto; max-inline-size: var(--main-width); - padding-inline: 1ch; @media (min-width: 640px) { --card-grid-columns: 2; @@ -292,7 +285,13 @@ } .cards__list { - display: contents; + align-items: start; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: var(--cards-gap); + justify-content: center; + padding: 1ch; } .card { diff --git a/app/views/boards/show/_columns.html.erb b/app/views/boards/show/_columns.html.erb index 25ed093d7..191d18cbc 100644 --- a/app/views/boards/show/_columns.html.erb +++ b/app/views/boards/show/_columns.html.erb @@ -27,7 +27,7 @@ <%= render "boards/show/not_now", board: board %>
- <%= render "boards/show/maybe", 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? ] } %> diff --git a/app/views/boards/show/_maybe.html.erb b/app/views/boards/show/_stream.html.erb similarity index 100% rename from app/views/boards/show/_maybe.html.erb rename to app/views/boards/show/_stream.html.erb diff --git a/app/views/columns/cards/drops/streams/create.turbo_stream.erb b/app/views/columns/cards/drops/streams/create.turbo_stream.erb index 3141b847f..d197830ad 100644 --- a/app/views/columns/cards/drops/streams/create.turbo_stream.erb +++ b/app/views/columns/cards/drops/streams/create.turbo_stream.erb @@ -1 +1 @@ -<%= turbo_stream.replace("maybe", partial: "boards/show/maybe", method: :morph, locals:{ board: @card.board, page: @page }) %> +<%= turbo_stream.replace("maybe", partial: "boards/show/stream", method: :morph, locals:{ board: @card.board, page: @page }) %> diff --git a/app/views/public/boards/show/_columns.html.erb b/app/views/public/boards/show/_columns.html.erb index de0948491..59f2b5e39 100644 --- a/app/views/public/boards/show/_columns.html.erb +++ b/app/views/public/boards/show/_columns.html.erb @@ -10,7 +10,7 @@ <%= render "public/boards/show/not_now", board: board %>
- <%= render "public/boards/show/maybe", board: board, page: page %> + <%= render "public/boards/show/stream", board: board, page: page %>
<%= render partial: "public/boards/show/column", collection: board.columns, cached: true %> From 8f2ff62f7f6d7efa9ed4a09cd691a24ab6207c1a Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Tue, 6 Jan 2026 13:58:49 -0600 Subject: [PATCH 52/65] Fix public boards --- app/assets/stylesheets/card-columns.css | 7 ++++++- app/views/public/boards/show/_columns.html.erb | 1 + app/views/public/boards/show/_stream.html.erb | 10 ++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 4f3f9de4a..210892472 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -182,10 +182,14 @@ .cards__transition-container { block-size: 100%; border-radius: calc(var(--column-width-collapsed) / 2); - translate: 0 0.5ch; // Allow a little room for the mini bubble */ + translate: 0 0.5ch; /* Allow a little room for the mini bubble */ transition: translate var(--column-transition-duration) var(--ease-out-overshoot-subtle); @media (min-width: 640px) { + .is-expanded & { + translate: 0 0.5ch; /* Allow a little room for the mini bubble */ + } + .is-collapsed & { translate: 0 var(--column-width-collapsed); } @@ -366,6 +370,7 @@ font-weight: 600; gap: 0.5ch; grid-area: expander; + justify-content: center; outline: none; outline-offset: -2px; position: relative; diff --git a/app/views/public/boards/show/_columns.html.erb b/app/views/public/boards/show/_columns.html.erb index 59f2b5e39..17db6228e 100644 --- a/app/views/public/boards/show/_columns.html.erb +++ b/app/views/public/boards/show/_columns.html.erb @@ -3,6 +3,7 @@ data-controller="collapsible-columns" data-collapsible-columns-board-value="<%= board.id %>" data-collapsible-columns-collapsed-class="is-collapsed" + data-collapsible-columns-expanded-class="is-expanded" data-collapsible-columns-no-transitions-class="no-transitions" data-collapsible-columns-title-not-visible-class="is-off-screen"> diff --git a/app/views/public/boards/show/_stream.html.erb b/app/views/public/boards/show/_stream.html.erb index 9d041c1cd..08037800b 100644 --- a/app/views/public/boards/show/_stream.html.erb +++ b/app/views/public/boards/show/_stream.html.erb @@ -1,11 +1,17 @@ -
-
+ + <%# render "boards/show/expander", title: "Maybe?", count: column.cards.active.count, column_id: dom_id(column) %> + <%= render "boards/show/expander", title: "Maybe?", count: 2, column_id: "maybe" %> + <%= link_to public_board_columns_stream_url(board.publication.key), class: "cards__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> <%= icon_tag "grid", class: "translucent" %> Maximize column From b5c44e7708ac70e71eb1f2ebcc11f8e8fe61b24a Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Tue, 6 Jan 2026 14:06:04 -0600 Subject: [PATCH 53/65] Ensure filters sit on top of cards --- app/assets/stylesheets/filters.css | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/filters.css b/app/assets/stylesheets/filters.css index 800e1b31b..082fcf2fc 100644 --- a/app/assets/stylesheets/filters.css +++ b/app/assets/stylesheets/filters.css @@ -12,6 +12,7 @@ padding-block-start: 2px; /* prevents input focus-ring clipping on mobile */ position: relative; view-transition-name: filters; + z-index: 1; .btn { --btn-border-color: var(--color-ink-medium); From 3efe6bf4841492b967b7061f3a44793cd4e3aba8 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Tue, 6 Jan 2026 15:35:16 -0600 Subject: [PATCH 54/65] Use existing no_filtering_url to direct back to the board when clearing filters --- app/views/filters/settings/_manage.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/filters/settings/_manage.html.erb b/app/views/filters/settings/_manage.html.erb index 258169e0f..ef800df72 100644 --- a/app/views/filters/settings/_manage.html.erb +++ b/app/views/filters/settings/_manage.html.erb @@ -1,7 +1,7 @@ <% filter = user_filtering.filter %>
- <%= link_to root_path, class: "btn btn--remove txt-x-small", data: { controller: "hotkey tooltip", action: "keydown.esc@document->hotkey#click"} do %> + <%= link_to no_filtering_url, class: "btn btn--remove txt-x-small", data: { controller: "hotkey tooltip", action: "keydown.esc@document->hotkey#click"} do %> <%= icon_tag "close" %> Clear all <% end %> From 9f40571a9755ad9b758b68c4504586242388f834 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Tue, 6 Jan 2026 15:36:42 -0600 Subject: [PATCH 55/65] Revert "Use existing no_filtering_url to direct back to the board when clearing filters" This reverts commit 3efe6bf4841492b967b7061f3a44793cd4e3aba8. --- app/views/filters/settings/_manage.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/filters/settings/_manage.html.erb b/app/views/filters/settings/_manage.html.erb index ef800df72..258169e0f 100644 --- a/app/views/filters/settings/_manage.html.erb +++ b/app/views/filters/settings/_manage.html.erb @@ -1,7 +1,7 @@ <% filter = user_filtering.filter %>
- <%= link_to no_filtering_url, class: "btn btn--remove txt-x-small", data: { controller: "hotkey tooltip", action: "keydown.esc@document->hotkey#click"} do %> + <%= link_to root_path, class: "btn btn--remove txt-x-small", data: { controller: "hotkey tooltip", action: "keydown.esc@document->hotkey#click"} do %> <%= icon_tag "close" %> Clear all <% end %> From 218403f9d47d82baa8747a37402dcd2e4eb6bf0e Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Tue, 6 Jan 2026 15:37:40 -0600 Subject: [PATCH 56/65] Go back to board after clearing filters --- app/views/filters/settings/_manage.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/filters/settings/_manage.html.erb b/app/views/filters/settings/_manage.html.erb index 258169e0f..ef800df72 100644 --- a/app/views/filters/settings/_manage.html.erb +++ b/app/views/filters/settings/_manage.html.erb @@ -1,7 +1,7 @@ <% filter = user_filtering.filter %>
- <%= link_to root_path, class: "btn btn--remove txt-x-small", data: { controller: "hotkey tooltip", action: "keydown.esc@document->hotkey#click"} do %> + <%= link_to no_filtering_url, class: "btn btn--remove txt-x-small", data: { controller: "hotkey tooltip", action: "keydown.esc@document->hotkey#click"} do %> <%= icon_tag "close" %> Clear all <% end %> From 776c67a501530f9ce639176a23080d710e480635 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Tue, 6 Jan 2026 15:57:36 -0600 Subject: [PATCH 57/65] Use notch class so Save button is positioned correctly --- app/views/cards/container/_save_button.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/cards/container/_save_button.html.erb b/app/views/cards/container/_save_button.html.erb index ec213740b..45d836293 100644 --- a/app/views/cards/container/_save_button.html.erb +++ b/app/views/cards/container/_save_button.html.erb @@ -1,4 +1,4 @@ -
+
<%= button_tag type: :submit, form: dom_id(card, :edit_form), class: "btn borderless", title: "Save changes (#{ hotkey_label(["ctrl", "enter"]) })", data: { controller: "hotkey", action: "keydown.ctrl+enter@document->hotkey#click keydown.meta+enter@document->hotkey#click" } do %> From 6d05ab428e6177050eb1086bcc4d0ec84cef4a07 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Fri, 19 Dec 2025 13:10:11 +0100 Subject: [PATCH 58/65] Remove engagements We are not using this anymore! --- .../cards/engagements_controller.rb | 18 ------------------ app/models/card/engagement.rb | 6 ------ .../20251219120755_drop_card_engagements.rb | 16 ++++++++++++++++ db/schema.rb | 12 +----------- db/schema_sqlite.rb | 12 +----------- test/fixtures/card/engagements.yml | 4 ---- 6 files changed, 18 insertions(+), 50 deletions(-) delete mode 100644 app/controllers/cards/engagements_controller.rb delete mode 100644 app/models/card/engagement.rb create mode 100644 db/migrate/20251219120755_drop_card_engagements.rb delete mode 100644 test/fixtures/card/engagements.yml diff --git a/app/controllers/cards/engagements_controller.rb b/app/controllers/cards/engagements_controller.rb deleted file mode 100644 index 73b8e9c9d..000000000 --- a/app/controllers/cards/engagements_controller.rb +++ /dev/null @@ -1,18 +0,0 @@ -class Cards::EngagementsController < ApplicationController - include CardScoped - - def create - case params[:engagement] - when "doing" - @card.engage - when "on_deck" - @card.move_to_on_deck - end - render_card_replacement - end - - def destroy - @card.reconsider - render_card_replacement - end -end diff --git a/app/models/card/engagement.rb b/app/models/card/engagement.rb deleted file mode 100644 index 2f45543dc..000000000 --- a/app/models/card/engagement.rb +++ /dev/null @@ -1,6 +0,0 @@ -class Card::Engagement < ApplicationRecord - belongs_to :account, default: -> { card.account } - belongs_to :card, class_name: "::Card", touch: true - - validates :status, presence: true, inclusion: { in: %w[doing on_deck] } -end diff --git a/db/migrate/20251219120755_drop_card_engagements.rb b/db/migrate/20251219120755_drop_card_engagements.rb new file mode 100644 index 000000000..634d64456 --- /dev/null +++ b/db/migrate/20251219120755_drop_card_engagements.rb @@ -0,0 +1,16 @@ +class DropCardEngagements < ActiveRecord::Migration[8.2] + def up + drop_table :card_engagements + end + + def down + create_table :card_engagements, id: :uuid do |t| + t.references :account, type: :uuid, null: false + t.references :card, type: :uuid, null: false, index: true + t.string :status, null: false + t.timestamps + end + + add_index :card_engagements, [ :account_id, :status ] + end +end diff --git a/db/schema.rb b/db/schema.rb index 645cb7a96..c120a87ae 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.2].define(version: 2025_12_10_054934) do +ActiveRecord::Schema[8.2].define(version: 2025_12_19_120755) do create_table "accesses", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.datetime "accessed_at" t.uuid "account_id", null: false @@ -169,16 +169,6 @@ ActiveRecord::Schema[8.2].define(version: 2025_12_10_054934) do t.index ["card_id"], name: "index_card_activity_spikes_on_card_id", unique: true end - create_table "card_engagements", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| - t.uuid "account_id", null: false - t.uuid "card_id" - t.datetime "created_at", null: false - t.string "status", default: "doing", null: false - t.datetime "updated_at", null: false - t.index ["account_id", "status"], name: "index_card_engagements_on_account_id_and_status" - t.index ["card_id"], name: "index_card_engagements_on_card_id" - end - create_table "card_goldnesses", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.uuid "account_id", null: false t.uuid "card_id", null: false diff --git a/db/schema_sqlite.rb b/db/schema_sqlite.rb index 84fd827bb..68b3ea422 100644 --- a/db/schema_sqlite.rb +++ b/db/schema_sqlite.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.2].define(version: 2025_12_10_054934) do +ActiveRecord::Schema[8.2].define(version: 2025_12_19_120755) do create_table "accesses", id: :uuid, force: :cascade do |t| t.datetime "accessed_at" t.uuid "account_id", null: false @@ -169,16 +169,6 @@ ActiveRecord::Schema[8.2].define(version: 2025_12_10_054934) do t.index ["card_id"], name: "index_card_activity_spikes_on_card_id", unique: true end - create_table "card_engagements", id: :uuid, force: :cascade do |t| - t.uuid "account_id", null: false - t.uuid "card_id" - t.datetime "created_at", null: false - t.string "status", limit: 255, default: "doing", null: false - t.datetime "updated_at", null: false - t.index ["account_id", "status"], name: "index_card_engagements_on_account_id_and_status" - t.index ["card_id"], name: "index_card_engagements_on_card_id" - end - create_table "card_goldnesses", id: :uuid, force: :cascade do |t| t.uuid "account_id", null: false t.uuid "card_id", null: false diff --git a/test/fixtures/card/engagements.yml b/test/fixtures/card/engagements.yml deleted file mode 100644 index 8c96bd4b5..000000000 --- a/test/fixtures/card/engagements.yml +++ /dev/null @@ -1,4 +0,0 @@ -logo: - id: <%= ActiveRecord::FixtureSet.identify("logo_engagement", :uuid) %> - account: 37s_uuid - card: logo_uuid From 0417639049f9be1bd225ae833b5da341fbce5ef1 Mon Sep 17 00:00:00 2001 From: Dmytro Shteflyuk Date: Mon, 22 Dec 2025 16:54:47 -0500 Subject: [PATCH 59/65] Restore log level configurability in production environment --- config/environments/production.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/environments/production.rb b/config/environments/production.rb index 1927557cf..03a9df677 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -90,6 +90,11 @@ Rails.application.configure do # Prepend all log lines with the following tags. config.log_tags = [ :request_id ] + # "info" includes generic and useful information about system operation, but avoids logging too much + # information to avoid inadvertent exposure of personally identifiable information (PII). If you + # want to log everything, set the level to "debug". + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + # Use a different cache store in production. config.cache_store = :solid_cache_store From a7c9b7479c48f11dc5108e0ea8781e50084badf7 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Wed, 7 Jan 2026 11:22:12 -0500 Subject: [PATCH 60/65] saas: move log_level setting into an environment variable --- saas/config/deploy.beta.yml | 1 + saas/config/deploy.production.yml | 1 + saas/config/deploy.staging.yml | 1 + saas/config/environments/production.rb | 3 +-- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/saas/config/deploy.beta.yml b/saas/config/deploy.beta.yml index 18f4b8cbc..14bf89698 100644 --- a/saas/config/deploy.beta.yml +++ b/saas/config/deploy.beta.yml @@ -76,6 +76,7 @@ env: clear: APP_FQDN: beta<%= @beta_number %>.fizzy-beta.com RAILS_ENV: beta + RAILS_LOG_LEVEL: fatal # suppress unstructured log lines MYSQL_DATABASE_HOST: fizzy-mysql-primary MYSQL_DATABASE_REPLICA_HOST: fizzy-mysql-replica MYSQL_SOLID_CABLE_HOST: fizzy-mysql-primary diff --git a/saas/config/deploy.production.yml b/saas/config/deploy.production.yml index 443bc81e8..eadb66e7f 100644 --- a/saas/config/deploy.production.yml +++ b/saas/config/deploy.production.yml @@ -27,6 +27,7 @@ ssh: env: clear: RAILS_ENV: production + RAILS_LOG_LEVEL: fatal # suppress unstructured log lines MYSQL_DATABASE_HOST: fizzy-mysql-primary MYSQL_DATABASE_REPLICA_HOST: fizzy-mysql-replica MYSQL_SOLID_CABLE_HOST: fizzy-mysql-primary diff --git a/saas/config/deploy.staging.yml b/saas/config/deploy.staging.yml index bfcc5f441..7db5ef4bf 100644 --- a/saas/config/deploy.staging.yml +++ b/saas/config/deploy.staging.yml @@ -27,6 +27,7 @@ ssh: env: clear: RAILS_ENV: staging + RAILS_LOG_LEVEL: fatal # suppress unstructured log lines MYSQL_DATABASE_HOST: fizzy-staging-mysql-primary MYSQL_DATABASE_REPLICA_HOST: fizzy-staging-mysql-replica MYSQL_SOLID_CABLE_HOST: fizzy-staging-mysql-primary diff --git a/saas/config/environments/production.rb b/saas/config/environments/production.rb index 9cb886ed8..5f2579162 100644 --- a/saas/config/environments/production.rb +++ b/saas/config/environments/production.rb @@ -1,9 +1,8 @@ Rails.application.configure do config.active_storage.service = :purestorage - # Enable structured logging, suppress unstructured log lines + # Enable structured logging config.structured_logging.logger = ActiveSupport::Logger.new(STDOUT) - config.log_level = :fatal config.action_controller.default_url_options = { host: "app.fizzy.do", protocol: "https" } config.action_mailer.default_url_options = { host: "app.fizzy.do", protocol: "https" } From 897cac06c7ce222aafcd169df47c957f1b1d3d41 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 7 Jan 2026 11:36:31 -0600 Subject: [PATCH 61/65] Pull board-tools outside of the list for mobile --- app/assets/stylesheets/card-columns.css | 34 ++++++++++++++----- app/helpers/accesses_helper.rb | 4 ++- app/views/boards/show.html.erb | 3 +- .../columns/show/_add_card_button.html.erb | 2 -- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 210892472..3ed8a77bf 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -8,17 +8,15 @@ block-size: 100dvh; grid-template-rows: 1fr var(--footer-height); - #global-container, - #main { + #global-container { display: grid; grid-template-rows: auto 1fr; - } - - #global-container { overflow: hidden; } #main { + display: grid; + grid-template-rows: auto auto 1fr; overflow: auto; padding: 0; } @@ -496,7 +494,7 @@ /* Override card styles within columns /* ------------------------------------------------------------------------ */ - .cards { + .body--boards { .card { --block-space: 1em; --block-space-half: 0.5em; @@ -646,6 +644,24 @@ inline-size: auto; text-align: center; + @media (max-width: 639px) { + /* On mobile, hide the tool card inside the Maybe column */ + .cards & { + display: none; + } + + #cards_container > & { + margin: 0 3ch 1ch; + } + } + + @media (min-width: 640px) { + /* On desktop, hide the tool card above the columns */ + #cards_container > & { + display: none; + } + } + @media (min-width: 800px) { margin: var(--column-padding) var(--column-padding) 0; } @@ -654,8 +670,8 @@ z-index: 5; } - .separator--horizontal { - margin-block: var(--block-space); + .divider { + --divider-color: oklch(var(--lch-blue-light)); } .btn--link { @@ -669,7 +685,7 @@ footer { font-size: var(--text-x-small); - margin-block-end: calc(var(--block-space-half) * -1); + margin-block: 1ch calc(var(--block-space-half) * -1); } .overflow-count { diff --git a/app/helpers/accesses_helper.rb b/app/helpers/accesses_helper.rb index 69ef2efec..1ace03574 100644 --- a/app/helpers/accesses_helper.rb +++ b/app/helpers/accesses_helper.rb @@ -30,7 +30,9 @@ module AccessesHelper displayed_watchers = watchers.first(8) overflow_count = watchers.size - 8 - tag.strong(watchers.any? ? "Watching for new cards" : "No one is watching for new cards", class: "txt-uppercase") + + tag.div(class: "divider divider--fade") do + tag.strong(watchers.any? ? "Watching for new cards" : "No one is watching for new cards", class: "txt-uppercase") + end + tag.div(avatar_tags(displayed_watchers), class: "board-tools__watching") do tag.div(data: { controller: "dialog", action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside" }) do tag.button("+#{overflow_count}", class: "overflow-count btn btn--circle borderless", data: { action: "dialog#open" }, aria: { label: "Show #{overflow_count} more watchers" }) + diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb index e1b0a3e96..c6d6d4bbe 100644 --- a/app/views/boards/show.html.erb +++ b/app/views/boards/show.html.erb @@ -1,5 +1,5 @@ <% @page_title = @board.name %> -<% @body_class = "contained-scrolling" %> +<% @body_class = "body--boards contained-scrolling" %> <% turbo_exempts_page_from_cache %> <%= turbo_stream_from @board %> @@ -26,6 +26,7 @@ <% if @filter.used?(ignore_boards: true) %> <%= render "boards/show/filtered_cards", page: @page %> <% else %> + <%= render "columns/show/add_card_button", board: @board %> <%= render "boards/show/columns", page: @page, board: @board %> <% end %> <% end %> diff --git a/app/views/columns/show/_add_card_button.html.erb b/app/views/columns/show/_add_card_button.html.erb index 7be6297a7..974a88528 100644 --- a/app/views/columns/show/_add_card_button.html.erb +++ b/app/views/columns/show/_add_card_button.html.erb @@ -5,8 +5,6 @@ C <% end %> - -
<%= access_involvement_advance_button(board, Current.user, show_watchers: true) %>
From 05c00aa1b80a19498e1e9d740f1fd48d2c8ca768 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 7 Jan 2026 12:14:50 -0600 Subject: [PATCH 62/65] Fix public layout --- app/views/layouts/public.html.erb | 32 ++++++++++++++------------- app/views/public/boards/show.html.erb | 1 + 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/app/views/layouts/public.html.erb b/app/views/layouts/public.html.erb index 98a634e66..e728cc8d4 100644 --- a/app/views/layouts/public.html.erb +++ b/app/views/layouts/public.html.erb @@ -2,24 +2,26 @@ <%= render "layouts/shared/head" %> - - - <%= render "layouts/shared/flash" %> + <%= render "layouts/shared/flash" %> -
- <%= yield %> -
+
+ <%= yield %> +
+