From 2172ccfd7888db48c7eb47a2bd92f71ba2452256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20=C3=81lvarez?= Date: Wed, 1 Oct 2025 17:32:31 +0200 Subject: [PATCH 01/47] Move load-balancer to fizzy-staging-lb-01 --- config/deploy.staging.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/deploy.staging.yml b/config/deploy.staging.yml index c0b6502e4..e1ab533ec 100644 --- a/config/deploy.staging.yml +++ b/config/deploy.staging.yml @@ -39,8 +39,7 @@ accessories: load-balancer: image: basecamp/kamal-proxy:lb - roles: - - web + host: fizzy-staging-lb-01 options: publish: - 80:80 From b7b5bcc7b9b6ff7019137704a25be52a574691e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20=C3=81lvarez?= Date: Wed, 1 Oct 2025 19:00:31 +0200 Subject: [PATCH 02/47] Move load-balancer to fizzy-beta-lb-01 --- config/deploy.beta.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/deploy.beta.yml b/config/deploy.beta.yml index 9720dd8db..3b56efcfa 100644 --- a/config/deploy.beta.yml +++ b/config/deploy.beta.yml @@ -39,8 +39,7 @@ accessories: load-balancer: image: basecamp/kamal-proxy:lb - roles: - - web + host: fizzy-beta-lb-01 options: publish: - 80:80 From 8627f8829f005fcc4a6400daa9009c4a87bea32c Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 1 Oct 2025 13:24:29 -0500 Subject: [PATCH 03/47] Prefer single key hotkeys We'll still support cmd/ctrl + J for legacy muscle memory but new customer should just use `J` --- app/views/filters/menu/_button.html.erb | 2 +- app/views/filters/menu/_shortcut_notice.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/filters/menu/_button.html.erb b/app/views/filters/menu/_button.html.erb index b9f2e1835..3324272d3 100644 --- a/app/views/filters/menu/_button.html.erb +++ b/app/views/filters/menu/_button.html.erb @@ -1,6 +1,6 @@ <%= tag.button class:"fizzy-menu-trigger input input--select center flex-inline align-center txt-normal", data: { - action: "click->dialog#open:stop keydown.meta+j@document->hotkey#click keydown.ctrl+j@document->hotkey#click", + action: "click->dialog#open:stop keydown.j@document->hotkey#click keydown.meta+j@document->hotkey#click keydown.ctrl+j@document->hotkey#click", controller: "hotkey" } do %> diff --git a/app/views/filters/menu/_shortcut_notice.html.erb b/app/views/filters/menu/_shortcut_notice.html.erb index f4c28b43f..b594b19a0 100644 --- a/app/views/filters/menu/_shortcut_notice.html.erb +++ b/app/views/filters/menu/_shortcut_notice.html.erb @@ -1,5 +1,5 @@ <% if platform.desktop? %>
- Press <%= hotkey_label(["ctrl", "J"]) -%> anytime to open this, esc to close, to move, enter to navigate, SHIFT+ENTER to select. + Press J anytime to open this, esc to close, to move, enter to navigate, SHIFT+ENTER to select.
<% end %> From ca81e2aa36a74bda0336cd1fa881a09e07f6001b Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 1 Oct 2025 13:31:17 -0500 Subject: [PATCH 04/47] Prevent card bubbles getting clipped in grid layout --- 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 b6753394e..f4ff6ac05 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -73,7 +73,7 @@ z-index: 3; } - &:not(.cards--considering, .is-collapsed) { + &:not(.cards--considering, .is-collapsed, .cards--grid) { overflow: clip; } From 3ae82da19ef63cbd5645da1c602766c6d720aa17 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 1 Oct 2025 13:32:15 -0500 Subject: [PATCH 05/47] No longer needed --- app/views/collections/edit.html.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/collections/edit.html.erb b/app/views/collections/edit.html.erb index b6b0efe6c..39518d583 100644 --- a/app/views/collections/edit.html.erb +++ b/app/views/collections/edit.html.erb @@ -38,7 +38,6 @@
- <%# render "collections/edit/workflows", collection: @collection %> <%= render "collections/edit/auto_close", collection: @collection %> <%= render "collections/edit/publication", collection: @collection %> <%= render "collections/edit/delete", collection: @collection %> From 935c503ac5acb1e1c4ce7d5e172436a9e57d8544 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 1 Oct 2025 13:33:38 -0500 Subject: [PATCH 06/47] Replace old link with proper collection path --- app/views/collections/edit.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/collections/edit.html.erb b/app/views/collections/edit.html.erb index 39518d583..f565cc972 100644 --- a/app/views/collections/edit.html.erb +++ b/app/views/collections/edit.html.erb @@ -3,7 +3,7 @@ <% content_for :header do %> <%= render "filters/menu", user_filtering: @user_filtering %>
- <%= link_to cards_path(collection_ids: [ @collection ]), class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %> + <%= link_to @collection, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %> <%= @collection.name %> From 113f8f299d8be360a835747dee490aecb294ef86 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 1 Oct 2025 13:37:48 -0500 Subject: [PATCH 07/47] Remove note about selection --- app/views/filters/menu/_shortcut_notice.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/filters/menu/_shortcut_notice.html.erb b/app/views/filters/menu/_shortcut_notice.html.erb index f4c28b43f..ed1d1fb70 100644 --- a/app/views/filters/menu/_shortcut_notice.html.erb +++ b/app/views/filters/menu/_shortcut_notice.html.erb @@ -1,5 +1,5 @@ <% if platform.desktop? %>
- Press <%= hotkey_label(["ctrl", "J"]) -%> anytime to open this, esc to close, to move, enter to navigate, SHIFT+ENTER to select. + Press <%= hotkey_label(["ctrl", "J"]) -%> anytime to open this, esc to close, to move, enter to navigate.
<% end %> From aca06d4fdff1521545714ada10e358e7cc38154c Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 1 Oct 2025 13:38:32 -0500 Subject: [PATCH 08/47] Don't break inside this clause --- app/views/filters/menu/_shortcut_notice.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/filters/menu/_shortcut_notice.html.erb b/app/views/filters/menu/_shortcut_notice.html.erb index ed1d1fb70..feeda0c3f 100644 --- a/app/views/filters/menu/_shortcut_notice.html.erb +++ b/app/views/filters/menu/_shortcut_notice.html.erb @@ -1,5 +1,5 @@ <% if platform.desktop? %>
- Press <%= hotkey_label(["ctrl", "J"]) -%> anytime to open this, esc to close, to move, enter to navigate. + Press <%= hotkey_label(["ctrl", "J"]) -%> anytime to open this, esc to close, to move, enter to navigate.
<% end %> From a1e650fb501658b7e84f4bc0192f7047c9159205 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 1 Oct 2025 13:58:15 -0500 Subject: [PATCH 09/47] Remove filtering checkboxes, replace with icon; modify icon --- app/assets/images/collection.svg | 2 +- .../filters/menu/collections/_all_option.html.erb | 9 +-------- .../filters/menu/collections/_collection.html.erb | 14 +------------- 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/app/assets/images/collection.svg b/app/assets/images/collection.svg index 5d6249839..cdbe7d4da 100644 --- a/app/assets/images/collection.svg +++ b/app/assets/images/collection.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/views/filters/menu/collections/_all_option.html.erb b/app/views/filters/menu/collections/_all_option.html.erb index 4c933510f..288877618 100644 --- a/app/views/filters/menu/collections/_all_option.html.erb +++ b/app/views/filters/menu/collections/_all_option.html.erb @@ -2,14 +2,7 @@ <% if Current.user.collections.many? %> From e046227ebc34a14cdeff04f7a25cf76ca4fbf6c3 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 1 Oct 2025 16:15:15 -0500 Subject: [PATCH 13/47] No need for a prefix here --- app/views/filters/settings/_collections.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/filters/settings/_collections.html.erb b/app/views/filters/settings/_collections.html.erb index 1c3d2a428..fffdd65c6 100644 --- a/app/views/filters/settings/_collections.html.erb +++ b/app/views/filters/settings/_collections.html.erb @@ -6,7 +6,7 @@ action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside", filter_show: user_filtering.show_collections?, multi_selection_combobox_no_selection_label_value: "Collection…", - multi_selection_combobox_label_prefix_value: "Collection" } do %> + multi_selection_combobox_label_prefix_value: "" } do %> +<%= form_with url: events_path, + method: :get, class: "flex-inline position-relative", + data: { controller: "form", turbo_frame: "cards_container", turbo_action: "advance" } do |form| %> + <%= tag.div class: "flex-inline position-relative quick-filter", + data: { + controller: "dialog multi-selection-combobox", + action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside", + filter_show: user_filtering.show_collections?, + multi_selection_combobox_no_selection_label_value: user_filtering.selected_collections_label } do %> + - + - <%= filter_dialog "Collection…" do %> - Collection… + <%= filter_dialog "Collection…" do %> + Collection… - <% if user_filtering.collections.many? %> - <%= text_field_tag nil, nil, placeholder: "Filter…", class: "input input--transparent txt-small font-weight-normal", autofocus: true, - type: "search", autocorrect: "off", autocomplete: "off", data: { "1p-ignore": "true", filter_target: "input", action: "input->filter#filter" } %> - <% end %> - - + + <% end %> + <% end %> <% end %> From e2f0d2f85b038d9dcecef959bf7d6717f0359dc7 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 2 Oct 2025 10:26:04 +0200 Subject: [PATCH 17/47] Skip new turbo frame containing events --- app/views/events/event/_layout.html.erb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/events/event/_layout.html.erb b/app/views/events/event/_layout.html.erb index 48b25d0bd..69a1560e6 100644 --- a/app/views/events/event/_layout.html.erb +++ b/app/views/events/event/_layout.html.erb @@ -1,9 +1,11 @@ <%= link_to event.notifiable_target, class: "event event--#{ event.action } #{ "golden-effect" if event.card.golden? } center-block flex flex-column full-width align-start justify-start position-relative", style: "--card-color: #{ card.closed? ? "var(--color-card-complete)" : card.color };", - data: { related_element_target: "related", - related_element_group_value: card.id, - action: "mouseover->related-element#highlight mouseout->related-element#unhighlight" } do %> + target: "_top", + data: { + related_element_target: "related", + related_element_group_value: card.id, + action: "mouseover->related-element#highlight mouseout->related-element#unhighlight" } do %>

<%= card.id %> From 08c6a384928542620baf3b88dfdb006b50f82ec6 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 2 Oct 2025 10:30:14 +0200 Subject: [PATCH 18/47] Remove falling back status that we don't support anymore --- app/models/card.rb | 1 - app/models/filter/fields.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/card.rb b/app/models/card.rb index 9175d6b96..c837c781a 100644 --- a/app/models/card.rb +++ b/app/models/card.rb @@ -22,7 +22,6 @@ class Card < ApplicationRecord case index when "stalled" then stalled when "postponing_soon" then postponing_soon - when "falling_back_soon" then falling_back_soon when "closed" then closed.recently_closed_first when "golden" then golden when "draft" then drafted diff --git a/app/models/filter/fields.rb b/app/models/filter/fields.rb index fc11752fc..027ae8ac6 100644 --- a/app/models/filter/fields.rb +++ b/app/models/filter/fields.rb @@ -1,7 +1,7 @@ module Filter::Fields extend ActiveSupport::Concern - INDEXES = %w[ all stalled postponing_soon falling_back_soon golden draft ] + INDEXES = %w[ all stalled postponing_soon golden draft ] SORTED_BY = %w[ newest oldest latest ] delegate :default_value?, to: :class From 2f38cb3ecd4c9eeccfec9f05fe62b312f2530158 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 2 Oct 2025 10:30:32 +0200 Subject: [PATCH 19/47] Wire up collection filtering: it should be performed in the generic cards view --- app/javascript/controllers/filter_settings_controller.js | 8 +++++++- app/views/filters/_settings.html.erb | 3 ++- app/views/filters/settings/_collections.html.erb | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/javascript/controllers/filter_settings_controller.js b/app/javascript/controllers/filter_settings_controller.js index 7a207da80..6be549e5c 100644 --- a/app/javascript/controllers/filter_settings_controller.js +++ b/app/javascript/controllers/filter_settings_controller.js @@ -5,7 +5,7 @@ import { post } from "@rails/request.js" export default class extends Controller { static classes = ["filtersSet"] static targets = ["field", "form"] - static values = { refreshUrl: String, noFilteringUrl: String } + static values = { refreshUrl: String, noFilteringUrl: String, cardsUrl: String } initialize() { this.debouncedToggle = debounce(this.#toggle.bind(this), 50) @@ -31,6 +31,12 @@ export default class extends Controller { this.debouncedToggle() } + submitToGenericCardsView() { + this.formTarget.action = this.cardsUrlValue + this.formTarget.dataset.turboFrame = "top" + this.formTarget.requestSubmit() + } + #toggle() { this.element.classList.toggle(this.filtersSetClass, this.#hasFiltersSet) } diff --git a/app/views/filters/_settings.html.erb b/app/views/filters/_settings.html.erb index 16b37ba6d..dbd2f7b66 100644 --- a/app/views/filters/_settings.html.erb +++ b/app/views/filters/_settings.html.erb @@ -6,7 +6,8 @@ toggle_class_toggle_class: "filters--expanded", filter_settings_filters_set_class: "filters--has-filters-set", filter_settings_no_filtering_url_value: no_filtering_url, - filter_settings_refresh_url_value: settings_refresh_path } do %> + filter_settings_refresh_url_value: settings_refresh_path, + filter_settings_cards_url_value: cards_path } do %> <%= form_with url: filter_url, method: :get, class: "", data: { controller: "form", turbo_frame: "cards_container", diff --git a/app/views/filters/settings/_collections.html.erb b/app/views/filters/settings/_collections.html.erb index fffdd65c6..c1bf2d9c2 100644 --- a/app/views/filters/settings/_collections.html.erb +++ b/app/views/filters/settings/_collections.html.erb @@ -29,7 +29,7 @@ <%= tag.li class: "popup__item", data: { filter_target: "item", navigable_list_target: "item", multi_selection_combobox_target: "item", multi_selection_combobox_value: collection.id, multi_selection_combobox_label: collection.name }, role: "checkbox", aria: { checked: filter.collections.include?(collection) } do %> - From a198cb1e279bfa13ad5e4285078b3f061d67ac18 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 2 Oct 2025 14:04:05 +0200 Subject: [PATCH 20/47] Make permanent to avoid losing menu on page refreshes --- app/views/filters/_menu.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/filters/_menu.html.erb b/app/views/filters/_menu.html.erb index 7b84cdd60..a117bdfa7 100644 --- a/app/views/filters/_menu.html.erb +++ b/app/views/filters/_menu.html.erb @@ -1,4 +1,4 @@ -

<% end %> -
- <%= render "events/day", day_timeline: @day_timeline %> -
-<%= event_next_page_link(@day_timeline.next_day) %> +<%= tag.div id: "activity", class: "events", data: { controller: "pagination", pagination_paginate_on_intersection_value: true } do %> + <%= day_timeline_pagination_frame_tag @day_timeline do %> + <%= render "events/day", day_timeline: @day_timeline %> + + <%= day_timeline_pagination_link(@day_timeline, @filter) %> + <% end %> +<% end %> From bc90129af2e8bce2d227b7d3183c8e159c61808b Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 2 Oct 2025 15:45:09 +0200 Subject: [PATCH 25/47] Use frame based pagination for the users timeline too --- app/controllers/users_controller.rb | 10 +++++++++- app/views/events/event/_layout.html.erb | 2 +- app/views/users/_activity_timeline.html.erb | 11 +++++++++++ app/views/users/show.html.erb | 5 +---- 4 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 app/views/users/_activity_timeline.html.erb diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index d066632bd..11b793b17 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -24,7 +24,7 @@ class UsersController < ApplicationController def show @filter = Current.user.filters.new(creator_ids: [ @user.id ]) - @day_timeline = Current.user.timeline_for(Time.current, filter: @filter) + @day_timeline = Current.user.timeline_for(day_param, filter: @filter) end def update @@ -50,6 +50,14 @@ class UsersController < ApplicationController head :forbidden unless Current.user.can_change?(@user) end + def day_param + if params[:day].present? + Time.zone.parse(params[:day]) + else + Time.current + end + end + def user_params params.expect(user: [ :name, :email_address, :password, :avatar ]) end diff --git a/app/views/events/event/_layout.html.erb b/app/views/events/event/_layout.html.erb index 48b25d0bd..63f8e696d 100644 --- a/app/views/events/event/_layout.html.erb +++ b/app/views/events/event/_layout.html.erb @@ -1,6 +1,6 @@ <%= link_to event.notifiable_target, class: "event event--#{ event.action } #{ "golden-effect" if event.card.golden? } center-block flex flex-column full-width align-start justify-start position-relative", - style: "--card-color: #{ card.closed? ? "var(--color-card-complete)" : card.color };", + style: "--card-color: #{ card.closed? ? "var(--color-card-complete)" : card.color };", target: "_top", data: { related_element_target: "related", related_element_group_value: card.id, action: "mouseover->related-element#highlight mouseout->related-element#unhighlight" } do %> diff --git a/app/views/users/_activity_timeline.html.erb b/app/views/users/_activity_timeline.html.erb new file mode 100644 index 000000000..4bb6890de --- /dev/null +++ b/app/views/users/_activity_timeline.html.erb @@ -0,0 +1,11 @@ +
+<%= day_timeline_pagination_frame_tag day_timeline do %> + <%= render "events/day", day_timeline: day_timeline %> + + <%= link_to "Load more...", user_path(user, day: day_timeline.next_day.strftime("%Y-%m-%d"), **filter.as_params), + data: { frame: day_timeline_pagination_frame_id_for(day_timeline.next_day), pagination_target: "paginationLink" } %> + <% end %> +
+ + + diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 6ae8f520c..9e27338c3 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -50,7 +50,4 @@

<%= "What #{ Current.user == @user ? "have you" : "has #{ @user.first_name }" } been up to?" %>

-
- <%= render "events/day", day_timeline: @day_timeline %> -
-<%= event_next_page_link(@day_timeline.next_day) %> +<%= render "users/activity_timeline", user: @user, day_timeline: @day_timeline, filter: @filter %> From b986927e25ad86bbed227d85fb8f4bcf8ecb0aef Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 2 Oct 2025 15:54:02 +0200 Subject: [PATCH 26/47] Add pagination to the list of filtered cards --- app/helpers/pagination_helper.rb | 9 +++++++++ app/views/cards/index.html.erb | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb index 3fdbe4923..388c244dc 100644 --- a/app/helpers/pagination_helper.rb +++ b/app/helpers/pagination_helper.rb @@ -35,6 +35,15 @@ module PaginationHelper end end + def with_automatic_pagination(name, page, **properties) + pagination_list name, paginate_on_scroll: true, **properties do + concat(pagination_frame_tag(name, page) do + yield + concat link_to_next_page(name, page, activate_when_observed: true) + end) + end + end + def day_timeline_pagination_frame_tag(day_timeline, &) turbo_frame_tag day_timeline_pagination_frame_id_for(day_timeline.day), data: { timeline_target: "frame" }, role: "presentation", refresh: :morph, & end diff --git a/app/views/cards/index.html.erb b/app/views/cards/index.html.erb index 1e457c452..47d15dcb8 100644 --- a/app/views/cards/index.html.erb +++ b/app/views/cards/index.html.erb @@ -27,6 +27,8 @@ <%= turbo_frame_tag :cards_container do %>
- <%= render partial: "cards/display/preview", collection: @page.records, as: :card, locals: { draggable: true }, cached: ->(card) { cacheable_preview_parts_for(card) } %> + <%= with_automatic_pagination :cards_paginated_container, @page do %> + <%= render partial: "cards/display/preview", collection: @page.records, as: :card, locals: { draggable: true }, cached: ->(card) { cacheable_preview_parts_for(card) } %> + <% end %>
<% end %> From dbc2f47f80af19fece86fb1366b652d0f1152dfd Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 2 Oct 2025 15:58:27 +0200 Subject: [PATCH 27/47] Keep filter panel during page refreshes --- app/views/filters/_settings.html.erb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/filters/_settings.html.erb b/app/views/filters/_settings.html.erb index 16b37ba6d..5642197fb 100644 --- a/app/views/filters/_settings.html.erb +++ b/app/views/filters/_settings.html.erb @@ -1,11 +1,12 @@ <% cache user_filtering do %> - <%= tag.aside id: "filter-settings", + <%= tag.aside \ class: class_names("filters flex align-center gap-half justify-center center margin-block-end", { "filters--expanded": user_filtering.expanded? }), data: { controller: "toggle-class filter-settings", toggle_class_toggle_class: "filters--expanded", filter_settings_filters_set_class: "filters--has-filters-set", filter_settings_no_filtering_url_value: no_filtering_url, + turbo_permanent: true, filter_settings_refresh_url_value: settings_refresh_path } do %> <%= form_with url: filter_url, method: :get, class: "", data: { controller: "form", From 575d231650d5586310b1bd369cb9ec4416b5e59c Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 2 Oct 2025 16:04:39 +0200 Subject: [PATCH 28/47] Live updates for the collection perma --- app/views/collections/show.html.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/collections/show.html.erb b/app/views/collections/show.html.erb index ac7343885..96392f1d2 100644 --- a/app/views/collections/show.html.erb +++ b/app/views/collections/show.html.erb @@ -1,6 +1,8 @@ <% @page_title = @collection.name %> <% turbo_exempts_page_from_cache %> +<%= turbo_stream_from @collection %> + <% content_for :head do %> <%= tag.meta property: "og:title", content: @page_title %> <%= tag.meta property: "og:description", content: Account.sole.name %> From 44c3910699cf1f18efe6de8e40373ed2bd844b02 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 2 Oct 2025 16:10:00 +0200 Subject: [PATCH 29/47] Don't render next link unless there is more activity --- app/helpers/pagination_helper.rb | 6 ++++-- app/views/users/_activity_timeline.html.erb | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb index 388c244dc..ee665e98a 100644 --- a/app/helpers/pagination_helper.rb +++ b/app/helpers/pagination_helper.rb @@ -53,8 +53,10 @@ module PaginationHelper end def day_timeline_pagination_link(day_timeline, filter) - link_to "Load more...", events_days_path(day: day_timeline.next_day.strftime("%Y-%m-%d"), **filter.as_params), - data: { frame: day_timeline_pagination_frame_id_for(day_timeline.next_day), pagination_target: "paginationLink" } + if day_timeline.next_day + link_to "Load more...", events_days_path(day: day_timeline.next_day.strftime("%Y-%m-%d"), **filter.as_params), + data: { frame: day_timeline_pagination_frame_id_for(day_timeline.next_day), pagination_target: "paginationLink" } + end end private diff --git a/app/views/users/_activity_timeline.html.erb b/app/views/users/_activity_timeline.html.erb index 4bb6890de..1c3b84d56 100644 --- a/app/views/users/_activity_timeline.html.erb +++ b/app/views/users/_activity_timeline.html.erb @@ -1,9 +1,11 @@
-<%= day_timeline_pagination_frame_tag day_timeline do %> + <%= day_timeline_pagination_frame_tag day_timeline do %> <%= render "events/day", day_timeline: day_timeline %> - <%= link_to "Load more...", user_path(user, day: day_timeline.next_day.strftime("%Y-%m-%d"), **filter.as_params), - data: { frame: day_timeline_pagination_frame_id_for(day_timeline.next_day), pagination_target: "paginationLink" } %> + <% if day_timeline.next_day %> + <%= link_to "Load more...", user_path(user, day: day_timeline.next_day.strftime("%Y-%m-%d"), **filter.as_params), + data: { frame: day_timeline_pagination_frame_id_for(day_timeline.next_day), pagination_target: "paginationLink" } %> + <% end %> <% end %>
From 1b9d75277297c47b2e5835f3eb8220eb2dbeb59d Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 2 Oct 2025 16:15:18 +0200 Subject: [PATCH 30/47] Keep column popups during page refreshes --- app/views/collections/show/menu/_column.html.erb | 2 +- app/views/collections/show/menu/_columns.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/collections/show/menu/_column.html.erb b/app/views/collections/show/menu/_column.html.erb index 22d249126..fd0894fc8 100644 --- a/app/views/collections/show/menu/_column.html.erb +++ b/app/views/collections/show/menu/_column.html.erb @@ -1,4 +1,4 @@ -
+
From 9293d557c8a017dc0442799e692ecfa697efd7af Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 2 Oct 2025 17:12:18 +0200 Subject: [PATCH 31/47] Fix: this was getting idiomorph confused as it was being invoked on many subtree elements See https://fizzy.37signals.com/5986089/cards/2057 --- 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 84ef0d315..5002c46e2 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -14,7 +14,7 @@ export default class extends Controller { } preventToggle(event) { - if (event.detail.attributeName === "class") { + if (event.target.hasAttribute("data-collapsible-columns-target") && event.detail.attributeName === "class") { event.preventDefault() } } From c6893dcae8775d1e3861c6f282eae4c209905ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20=C3=81lvarez?= Date: Thu, 2 Oct 2025 10:34:50 +0200 Subject: [PATCH 32/47] Add otel_collector accessory for load balancer --- config/deploy.beta.yml | 11 +++++++++++ config/deploy.staging.yml | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/config/deploy.beta.yml b/config/deploy.beta.yml index 3b56efcfa..8aee0d69f 100644 --- a/config/deploy.beta.yml +++ b/config/deploy.beta.yml @@ -46,3 +46,14 @@ accessories: - 443:443 volumes: - load-balancer:/home/kamal-proxy/.config/kamal-proxy + + otel_collector-load-balancer: + image: otel/opentelemetry-collector-contrib:0.126.0 + port: 9394 + files: + - config/otel_collector.yml:/etc/otelcol-contrib/config.yaml + volumes: + - /var/run/docker.sock:/var/run/docker.sock + options: + user: 0 + host: fizzy-beta-lb-01 diff --git a/config/deploy.staging.yml b/config/deploy.staging.yml index e1ab533ec..4491bfb71 100644 --- a/config/deploy.staging.yml +++ b/config/deploy.staging.yml @@ -46,3 +46,14 @@ accessories: - 443:443 volumes: - load-balancer:/home/kamal-proxy/.config/kamal-proxy + + otel_collector-load-balancer: + image: otel/opentelemetry-collector-contrib:0.126.0 + port: 9394 + files: + - config/otel_collector.yml:/etc/otelcol-contrib/config.yaml + volumes: + - /var/run/docker.sock:/var/run/docker.sock + options: + user: 0 + host: fizzy-staging-lb-01 From 013f83b94aec034d440da805488e1ecbf971dd7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20=C3=81lvarez?= Date: Thu, 2 Oct 2025 17:35:53 +0200 Subject: [PATCH 33/47] Move load-balancer to fizzy-lb-101 --- config/deploy.production.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/config/deploy.production.yml b/config/deploy.production.yml index b7b755479..794ba4c98 100644 --- a/config/deploy.production.yml +++ b/config/deploy.production.yml @@ -39,11 +39,21 @@ accessories: load-balancer: image: basecamp/kamal-proxy:lb - roles: - - web + host: fizzy-lb-101 options: publish: - 80:80 - 443:443 volumes: - load-balancer:/home/kamal-proxy/.config/kamal-proxy + + otel_collector-load-balancer: + image: otel/opentelemetry-collector-contrib:0.126.0 + port: 9394 + files: + - config/otel_collector.yml:/etc/otelcol-contrib/config.yaml + volumes: + - /var/run/docker.sock:/var/run/docker.sock + options: + user: 0 + host: fizzy-lb-101 From 7129b711f116101d9f27ecc630b7cdd707ed4d6d Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 2 Oct 2025 12:41:41 -0400 Subject: [PATCH 34/47] Only render the manifest link for tenanted URLs --- app/views/layouts/shared/_head.html.erb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/shared/_head.html.erb b/app/views/layouts/shared/_head.html.erb index 0922c2de4..25b06cae9 100644 --- a/app/views/layouts/shared/_head.html.erb +++ b/app/views/layouts/shared/_head.html.erb @@ -19,7 +19,9 @@ <%= tenanted_action_cable_meta_tag %> <%= yield :head %> - + <% if ApplicationRecord.current_tenant %> + + <% end %> From 9518495ce2a8b8f8579886953dd8c44653ae6d81 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 2 Oct 2025 14:22:54 -0500 Subject: [PATCH 35/47] Reverse involvement buttons if watching --- app/helpers/accesses_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/accesses_helper.rb b/app/helpers/accesses_helper.rb index 8e3dee6a3..24a3fdbec 100644 --- a/app/helpers/accesses_helper.rb +++ b/app/helpers/accesses_helper.rb @@ -50,7 +50,7 @@ module AccessesHelper collection_involvement_path(collection), method: :put, aria: { labelledby: involvement_label_id }, - class: "btn", + class: class_names("btn", { "btn--reversed": access.involvement == "watching" }), params: { show_watchers: show_watchers, involvement: next_involvement(access.involvement), icon_only: icon_only } ) do safe_join([ From 6c06da6624420b05a533118842e64a647b2fea15 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Thu, 2 Oct 2025 14:31:07 -0500 Subject: [PATCH 36/47] Fix missing close bracket from merge --- app/views/filters/_settings.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/filters/_settings.html.erb b/app/views/filters/_settings.html.erb index f6fc431f4..b8f907c45 100644 --- a/app/views/filters/_settings.html.erb +++ b/app/views/filters/_settings.html.erb @@ -8,7 +8,7 @@ filter_settings_no_filtering_url_value: no_filtering_url, filter_settings_refresh_url_value: settings_refresh_path, filter_settings_cards_url_value: cards_path, - turbo_permanent: true do %> + turbo_permanent: true } do %> <%= form_with url: filter_url, method: :get, class: "", data: { controller: "form", turbo_frame: "cards_container", From 159a96a339926baa8b3076d8ce15ba5a157f234d Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Thu, 2 Oct 2025 14:31:14 -0500 Subject: [PATCH 37/47] Formatting --- app/views/filters/menu/custom/_filter.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/filters/menu/custom/_filter.html.erb b/app/views/filters/menu/custom/_filter.html.erb index 5ed324e03..b398a18a0 100644 --- a/app/views/filters/menu/custom/_filter.html.erb +++ b/app/views/filters/menu/custom/_filter.html.erb @@ -2,8 +2,8 @@ <%= icon_tag "bookmark", class: "popup__icon" %> <%= link_to cards_path(filter_id: filter.id), class: "popup__btn btn" do %>
-
<%= filter.collections_label %>
-
<%= filter.summary %>
+
<%= filter.collections_label %>
+
<%= filter.summary %>
<% end %> From 47370084d2a7aff99efc96f4fbca78d518b8830c Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Tue, 30 Sep 2025 13:03:08 -0400 Subject: [PATCH 38/47] Remove code related to Launchpad auth or 37id Users --- db/seeds.rb | 47 ++++--------------- .../concerns/sessions/signal_sessions.rb | 14 ------ .../sessions/launchpad_controller.rb | 23 --------- .../app/helpers/login_helper/signal_login.rb | 15 ------ .../app/models/account/signal_account.rb | 3 +- .../fizzy-saas/app/models/user/signal_user.rb | 19 -------- gems/fizzy-saas/config/routes.rb | 6 --- gems/fizzy-saas/lib/fizzy/saas/engine.rb | 4 -- .../controller_authentication_test.rb | 17 ------- .../sessions/launchpad_controller_test.rb | 38 --------------- .../controllers/sessions_controller_test.rb | 24 ---------- .../models/account/signal_account_test.rb | 8 ++-- gems/fizzy-saas/test/models/signup_test.rb | 2 - .../test/models/user/signal_user_test.rb | 22 --------- gems/fizzy-saas/test/test_helper.rb | 4 -- script/create-new-user.rb | 44 ++++++----------- script/load-prod-db-in-dev.rb | 1 - .../migrations/create-accounts-in-staging.rb | 21 --------- script/stitch-signal-id-accounts.rb | 41 ---------------- .../controller_authentication_test.rb | 18 ++++--- test/controllers/sessions_controller_test.rb | 40 ++++++++-------- test/fixtures/users.yml | 9 ---- test/test_helpers/session_test_helper.rb | 6 +-- 23 files changed, 55 insertions(+), 371 deletions(-) delete mode 100644 gems/fizzy-saas/app/controllers/concerns/sessions/signal_sessions.rb delete mode 100644 gems/fizzy-saas/app/controllers/sessions/launchpad_controller.rb delete mode 100644 gems/fizzy-saas/app/helpers/login_helper/signal_login.rb delete mode 100644 gems/fizzy-saas/app/models/user/signal_user.rb delete mode 100644 gems/fizzy-saas/test/controllers/controller_authentication_test.rb delete mode 100644 gems/fizzy-saas/test/controllers/sessions/launchpad_controller_test.rb delete mode 100644 gems/fizzy-saas/test/controllers/sessions_controller_test.rb delete mode 100644 gems/fizzy-saas/test/models/user/signal_user_test.rb delete mode 100755 script/migrations/create-accounts-in-staging.rb delete mode 100755 script/stitch-signal-id-accounts.rb diff --git a/db/seeds.rb b/db/seeds.rb index 74a616684..98954991d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -40,45 +40,16 @@ def create_tenant(signal_account_name, bare: false) end def find_or_create_user(full_name, email_address) - if Rails.application.config.x.local_authentication - if user = User.find_by(email_address: email_address) - user.password = "secret123456" - user.save! - user - else - User.create!( - name: full_name, - email_address: email_address, - password: "secret123456" - ) - end + if user = User.find_by(email_address: email_address) + user.password = "secret123456" + user.save! + user else - SignalId::Database.on_master do - unless signal_identity = SignalId::Identity.find_by_email_address(email_address) - signal_identity = SignalId::Identity.create!( - name: full_name, - email_address: email_address, - username: email_address, - password: "secret123456" - ) - end - - signal_account = Account.sole.external_account - signal_user = SignalId::User.find_or_create_by!(identity: signal_identity, account: signal_account) - - if user = User.find_by(external_user_id: signal_user.id) - user.password = "secret123456" - user.save! - user - else - User.create!( - external_user_id: signal_user.id, - name: signal_identity.name, - email_address: signal_identity.email_address, - password: "secret123456" - ) - end - end + User.create!( + name: full_name, + email_address: email_address, + password: "secret123456" + ) end end diff --git a/gems/fizzy-saas/app/controllers/concerns/sessions/signal_sessions.rb b/gems/fizzy-saas/app/controllers/concerns/sessions/signal_sessions.rb deleted file mode 100644 index 8d22dd8b7..000000000 --- a/gems/fizzy-saas/app/controllers/concerns/sessions/signal_sessions.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Sessions - module SignalSessions - extend ActiveSupport::Concern - - included do - before_action :require_local_auth, only: %i[ new create ] - end - - private - def require_local_auth - head :forbidden - end - end -end diff --git a/gems/fizzy-saas/app/controllers/sessions/launchpad_controller.rb b/gems/fizzy-saas/app/controllers/sessions/launchpad_controller.rb deleted file mode 100644 index 8c15ef596..000000000 --- a/gems/fizzy-saas/app/controllers/sessions/launchpad_controller.rb +++ /dev/null @@ -1,23 +0,0 @@ -class Sessions::LaunchpadController < ApplicationController - require_unauthenticated_access - - before_action :require_sig - - def show - end - - def update - user = Account.sole.external_account.authenticate(sig: @sig).try(:peer) - if user.present? && user.active? - start_new_session_for user - redirect_to after_authentication_url - else - render plain: "Authentication failed. This is probably a bug.", status: :unauthorized - end - end - - private - def require_sig - @sig = params.expect(:sig) - end -end diff --git a/gems/fizzy-saas/app/helpers/login_helper/signal_login.rb b/gems/fizzy-saas/app/helpers/login_helper/signal_login.rb deleted file mode 100644 index 69185e9c6..000000000 --- a/gems/fizzy-saas/app/helpers/login_helper/signal_login.rb +++ /dev/null @@ -1,15 +0,0 @@ -module LoginHelper - module SignalLogin - def login_url - if ApplicationRecord.current_tenant - Launchpad.login_url(product: true, account: Account.sole) - else - Launchpad.login_url(product: true) - end - end - - def logout_url - Launchpad.logout_url - end - end -end diff --git a/gems/fizzy-saas/app/models/account/signal_account.rb b/gems/fizzy-saas/app/models/account/signal_account.rb index 358f390a4..f117347dd 100644 --- a/gems/fizzy-saas/app/models/account/signal_account.rb +++ b/gems/fizzy-saas/app/models/account/signal_account.rb @@ -20,9 +20,8 @@ module Account::SignalAccount User.create!( name: account.external_account.owner.name, email_address: account.external_account.owner.email_address, - external_user_id: account.external_account.owner.id, role: "admin", - password: SecureRandom.hex(36) # TODO: remove password column? + password: SecureRandom.hex(16) ) end end diff --git a/gems/fizzy-saas/app/models/user/signal_user.rb b/gems/fizzy-saas/app/models/user/signal_user.rb deleted file mode 100644 index 8657c20ad..000000000 --- a/gems/fizzy-saas/app/models/user/signal_user.rb +++ /dev/null @@ -1,19 +0,0 @@ -module User::SignalUser - extend ActiveSupport::Concern - - prepended do - # the external_user_id is the SignalId::User id - belongs_to :external_user, dependent: :destroy, class_name: "SignalId::User", optional: true - end - - class_methods do - def find_by_signal_user_id(signal_user_id) - find_by(external_user_id: signal_user_id) - end - end - - def deactivate - super - SignalId::Database.on_master { external_user&.destroy } - end -end diff --git a/gems/fizzy-saas/config/routes.rb b/gems/fizzy-saas/config/routes.rb index 91d6df361..6fff83e03 100644 --- a/gems/fizzy-saas/config/routes.rb +++ b/gems/fizzy-saas/config/routes.rb @@ -1,10 +1,4 @@ Fizzy::Saas::Engine.routes.draw do - resource :session do - scope module: "sessions" do - resource :launchpad, only: %i[ show update ], controller: "launchpad" - end - end - namespace :signup do get "/" => "accounts#new" resources :accounts, only: %i[ new create ] diff --git a/gems/fizzy-saas/lib/fizzy/saas/engine.rb b/gems/fizzy-saas/lib/fizzy/saas/engine.rb index 2729e1db3..4e5e93489 100644 --- a/gems/fizzy-saas/lib/fizzy/saas/engine.rb +++ b/gems/fizzy-saas/lib/fizzy/saas/engine.rb @@ -5,10 +5,7 @@ module Fizzy class Engine < ::Rails::Engine # extend application models config.to_prepare do - User.prepend User::SignalUser Account.prepend Account::SignalAccount - LoginHelper.prepend LoginHelper::SignalLogin - SessionsController.include Sessions::SignalSessions end # moved from config/initializers/queenbee.rb @@ -39,7 +36,6 @@ module Fizzy silence_warnings do SignalId::Account::Peer = Account - SignalId::User::Peer = User end end diff --git a/gems/fizzy-saas/test/controllers/controller_authentication_test.rb b/gems/fizzy-saas/test/controllers/controller_authentication_test.rb deleted file mode 100644 index bf1fc21aa..000000000 --- a/gems/fizzy-saas/test/controllers/controller_authentication_test.rb +++ /dev/null @@ -1,17 +0,0 @@ -require "test_helper" - -class ControllerAuthenticationTest < ActionDispatch::IntegrationTest - test "access without an account slug redirects to launchpad" do - integration_session.default_url_options[:script_name] = "" # no tenant - - get cards_path - - assert_redirected_to Launchpad.login_url(product: true) - end - - test "access with an account slug but no session redirects to launchpad" do - get cards_path - - assert_redirected_to Launchpad.login_url(product: true, account: Account.sole) - end -end diff --git a/gems/fizzy-saas/test/controllers/sessions/launchpad_controller_test.rb b/gems/fizzy-saas/test/controllers/sessions/launchpad_controller_test.rb deleted file mode 100644 index 309f6ec1a..000000000 --- a/gems/fizzy-saas/test/controllers/sessions/launchpad_controller_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -require "test_helper" - -class Sessions::LaunchpadControllerTest < ActionDispatch::IntegrationTest - test "show renders when not signed in" do - get saas.session_launchpad_path(params: { sig: "test-sig" }) - - assert_response :success - - assert_select "form input#sig" do |node| - assert_equal node.length, 1 - assert_equal node.first["value"], "test-sig" - end - end - - test "create establishes a session when the sig is valid" do - user = users(:david) - - put saas.session_launchpad_path(params: { sig: user.external_user.perishable_signature }) - - assert_redirected_to root_url - assert parsed_cookies.signed[:session_token] - end - - test "create checks user.active?" do - user = users(:david) - user.update! active: false - - put saas.session_launchpad_path(params: { sig: user.external_user.perishable_signature }) - - assert_response :unauthorized - end - - test "returns 401 when the sig is invalid" do - put saas.session_launchpad_path(params: { sig: "invalid" }) - - assert_response :unauthorized - end -end diff --git a/gems/fizzy-saas/test/controllers/sessions_controller_test.rb b/gems/fizzy-saas/test/controllers/sessions_controller_test.rb deleted file mode 100644 index 98df25bdf..000000000 --- a/gems/fizzy-saas/test/controllers/sessions_controller_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require "test_helper" - -class SessionsControllerTest < ActionDispatch::IntegrationTest - test "destroy" do - sign_in_as :kevin - - delete session_path - - assert_redirected_to Launchpad.logout_url - assert_not cookies[:session_token].present? - end - - test "new" do - get new_session_path - - assert_response :forbidden - end - - test "create" do - post session_path, params: { email_address: "david@37signals.com", password: "secret123456" } - - assert_response :forbidden - end -end diff --git a/gems/fizzy-saas/test/models/account/signal_account_test.rb b/gems/fizzy-saas/test/models/account/signal_account_test.rb index 623f95610..ad43e45cb 100644 --- a/gems/fizzy-saas/test/models/account/signal_account_test.rb +++ b/gems/fizzy-saas/test/models/account/signal_account_test.rb @@ -18,7 +18,7 @@ class Account::SignalAccountTest < ActiveSupport::TestCase assert_equal @account, @account.external_account.peer end - test ".create_with_admin_user creates a new local account and user peers" do + test ".create_with_admin_user creates a new local account" do ApplicationRecord.create_tenant("account-create-with-dependents") do signal_account = signal_accounts(:honcho_fizzy) account = Account.create_with_admin_user(tenant_id: signal_account.queenbee_id) @@ -32,11 +32,9 @@ class Account::SignalAccountTest < ActiveSupport::TestCase assert_equal 1, User.count User.first.tap do |user| - assert signal_account.owner.name, user.name - assert signal_account.owner.email_address, user.email_address - assert signal_account.owner.id, user.external_user_id + assert_equal signal_account.owner.name, user.name + assert_equal signal_account.owner.email_address, user.email_address assert_equal "admin", user.role - assert_equal user, signal_account.owner.peer end end end diff --git a/gems/fizzy-saas/test/models/signup_test.rb b/gems/fizzy-saas/test/models/signup_test.rb index 96e432840..e64dbf801 100644 --- a/gems/fizzy-saas/test/models/signup_test.rb +++ b/gems/fizzy-saas/test/models/signup_test.rb @@ -42,7 +42,6 @@ class SignupTest < ActiveSupport::TestCase assert @signup.user assert @signup.user.persisted? - assert_equal @signup.user.external_user, @signup.signal_account.owner assert_equal @signup.queenbee_account.id.to_s, @signup.tenant_name assert_includes ApplicationRecord.tenants, @signup.tenant_name @@ -86,7 +85,6 @@ class SignupTest < ActiveSupport::TestCase assert @signup.user assert @signup.user.persisted? - assert_equal @signup.user.external_user, @signup.signal_account.owner assert_equal @signup.queenbee_account.id.to_s, @signup.tenant_name assert_includes ApplicationRecord.tenants, @signup.tenant_name diff --git a/gems/fizzy-saas/test/models/user/signal_user_test.rb b/gems/fizzy-saas/test/models/user/signal_user_test.rb deleted file mode 100644 index b244a587e..000000000 --- a/gems/fizzy-saas/test/models/user/signal_user_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require "test_helper" - -class User::SignalUserTest < ActiveSupport::TestCase - setup do - @user = users(:david) - end - - test "belongs to a Signal::User" do - assert_not_nil @user.external_user_id - assert_equal signal_users("37s_fizzy_david"), @user.external_user - end - - test "peering" do - assert_equal @user, @user.external_user.peer - end - - test "deactivate clears signal user" do - users(:jz).deactivate - - assert_nil users(:jz).reload.external_user - end -end diff --git a/gems/fizzy-saas/test/test_helper.rb b/gems/fizzy-saas/test/test_helper.rb index 22d468a6c..648afa3ee 100644 --- a/gems/fizzy-saas/test/test_helper.rb +++ b/gems/fizzy-saas/test/test_helper.rb @@ -4,10 +4,6 @@ require "queenbee/testing/mocks" module ActiveSupport class TestCase include SignalId::Testing - - def saas_extension_sign_in_as(user) - put saas.session_launchpad_path, params: { sig: user.external_user.perishable_signature } - end end end diff --git a/script/create-new-user.rb b/script/create-new-user.rb index 934719447..ab30c585e 100755 --- a/script/create-new-user.rb +++ b/script/create-new-user.rb @@ -2,41 +2,25 @@ require_relative "../config/environment" -if ARGV.length < 2 - puts "Usage: #{$0} " +if ARGV.length < 3 + puts "Usage: #{$0} " exit 1 end -email_address = ARGV[0] -tenant = ARGV[1] +tenant = ARGV.shift +email_address = ARGV.shift +name = ARGV.join(" ") -def confirm(noun) - print "Is this the correct #{noun}? (y/n) " - response = $stdin.gets.chomp.downcase - exit 0 unless response == "y" - puts -end - -signal_identity = SignalId::Identity.find_by!(email_address: email_address) -pp signal_identity -confirm "identity" - -ApplicationRecord.with_tenant(tenant) do - signal_account = Account.sole.external_account - pp signal_account - confirm "account" - - SignalId::Database.on_master do - signal_user = SignalId::User.create!(identity: signal_identity, account: signal_account) - - user = User.create!( - name: signal_user.name, - email_address: signal_user.email_address, - external_user_id: signal_user.id, - password: SecureRandom.hex(36) # TODO: remove password column? - ) +begin + ApplicationRecord.with_tenant(tenant) do + password = SecureRandom.hex(16) + user = User.create!(name:, email_address:, password:) puts "Created: " - pp [ user, signal_user ] + pp user + + puts "Password is: #{password.inspect}" end +rescue Exception => e + puts "Failed with error: #{e.inspect}" end diff --git a/script/load-prod-db-in-dev.rb b/script/load-prod-db-in-dev.rb index a619b1f8d..da0db12ba 100755 --- a/script/load-prod-db-in-dev.rb +++ b/script/load-prod-db-in-dev.rb @@ -34,7 +34,6 @@ ActiveRecord::Tenanted::DatabaseTasks.migrate_all ApplicationRecord.with_tenant(signup.tenant_name) do |tenant| Account.sole.update! external_account: signup.signal_account - User.first.update! external_user: signup.signal_account.owner puts "\n\nLogin to http://launchpad.localhost:3011/ as #{signup.email_address} / #{signup.password}" end diff --git a/script/migrations/create-accounts-in-staging.rb b/script/migrations/create-accounts-in-staging.rb deleted file mode 100755 index d0279d608..000000000 --- a/script/migrations/create-accounts-in-staging.rb +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env ruby - -require_relative "../config/environment" - -ApplicationRecord.with_each_tenant do |tenant| - account = Account.sole - signal_account = account.signal_account - - signal_users = SignalId::User.where(account_id: signal_account.id) - - signal_users.each do |signal_user| - unless User.find_by(signal_user_id: signal_user.id) - User.create!( - name: signal_user.identity.name, - email_address: signal_user.identity.email_address, - signal_user_id: signal_user.id, - password: SecureRandom.hex(36) # TODO: remove password column? - ) - end - end -end diff --git a/script/stitch-signal-id-accounts.rb b/script/stitch-signal-id-accounts.rb deleted file mode 100755 index e08f594ae..000000000 --- a/script/stitch-signal-id-accounts.rb +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env ruby - -# -# this is intended to copy a production database into beta, change the account id, and stitch the -# user accounts together properly. -# - -require_relative "../config/environment" - -ActiveRecord::Base.logger = Logger.new(File::NULL) - -ApplicationRecord.with_each_tenant do |tenant| - puts "\n# tenant: #{tenant}" - - signal_account = SignalId::Account.find_by!(queenbee_id: tenant) - puts "Found signal account #{signal_account.inspect}" - - account = Account.sole - if account.tenant_id != tenant - puts "setting account tenant_id to #{tenant}" - account.update!(tenant_id: tenant, name: account.name + " (Beta)") - end - - User.find_each do |user| - next if user.system? || user.external_user_id.nil? - - signal_user = user.external_user - next if signal_user.nil? - next if signal_user.account == account.external_account - - signal_identity = signal_user.identity - pp signal_identity - - SignalId::Database.on_master do - signal_user = SignalId::User.find_or_create_by!(identity: signal_identity, account: signal_account) - puts "Created signal user #{signal_user.inspect} for identity #{signal_identity.inspect}" - user.external_user_id = signal_user.id - user.save! - end - end -end diff --git a/test/controllers/controller_authentication_test.rb b/test/controllers/controller_authentication_test.rb index 1dfc7a3fd..02b729089 100644 --- a/test/controllers/controller_authentication_test.rb +++ b/test/controllers/controller_authentication_test.rb @@ -1,20 +1,18 @@ require "test_helper" class ControllerAuthenticationTest < ActionDispatch::IntegrationTest - if Rails.application.config.x.local_authentication - test "access without an account slug redirects to new session" do - integration_session.default_url_options[:script_name] = "" # no tenant + test "access without an account slug redirects to new session" do + integration_session.default_url_options[:script_name] = "" # no tenant - get cards_path + get cards_path - assert_redirected_to new_session_path - end + assert_redirected_to new_session_path + end - test "access with an account slug but no session redirects to new session" do - get cards_path + test "access with an account slug but no session redirects to new session" do + get cards_path - assert_redirected_to new_session_path - end + assert_redirected_to new_session_path end test "access with an account slug and a session allows functional access" do diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index 9a8cf0caf..0c85ad83c 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb @@ -1,34 +1,32 @@ require "test_helper" class SessionsControllerTest < ActionDispatch::IntegrationTest - if Rails.application.config.x.local_authentication - test "destroy" do - sign_in_as :kevin + test "destroy" do + sign_in_as :kevin - delete session_path + delete session_path - assert_redirected_to new_session_path - assert_not cookies[:session_token].present? - end + assert_redirected_to new_session_path + assert_not cookies[:session_token].present? + end - test "new" do - get new_session_path + test "new" do + get new_session_path - assert_response :success - end + assert_response :success + end - test "create with valid credentials" do - post session_path, params: { email_address: "david@37signals.com", password: "secret123456" } + test "create with valid credentials" do + post session_path, params: { email_address: "david@37signals.com", password: "secret123456" } - assert_redirected_to root_path - assert cookies[:session_token].present? - end + assert_redirected_to root_path + assert cookies[:session_token].present? + end - test "create with invalid credentials" do - post session_path, params: { email_address: "david@37signals.com", password: "wrong" } + test "create with invalid credentials" do + post session_path, params: { email_address: "david@37signals.com", password: "wrong" } - assert_redirected_to new_session_path - assert_not cookies[:session_token].present? - end + assert_redirected_to new_session_path + assert_not cookies[:session_token].present? end end diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 73c15ea65..d12c1a4b3 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -5,27 +5,18 @@ david: email_address: david@37signals.com password_digest: <%= digest %> role: member -<% if User.reflect_on_association :external_user %> - external_user: 37s_fizzy_david -<% end %> jz: name: JZ email_address: jz@37signals.com password_digest: <%= digest %> role: member -<% if User.reflect_on_association :external_user %> - external_user: 37s_fizzy_jzimdars -<% end %> kevin: name: Kevin email_address: kevin@37signals.com password_digest: <%= digest %> role: admin -<% if User.reflect_on_association :external_user %> - external_user: 37s_fizzy_kevin -<% end %> system: name: System diff --git a/test/test_helpers/session_test_helper.rb b/test/test_helpers/session_test_helper.rb index b04b06962..e857da327 100644 --- a/test/test_helpers/session_test_helper.rb +++ b/test/test_helpers/session_test_helper.rb @@ -7,11 +7,7 @@ module SessionTestHelper cookies.delete :session_token user = users(user) unless user.is_a? User - if Rails.application.config.x.local_authentication - post session_path, params: { email_address: user.email_address, password: "secret123456" } - else - saas_extension_sign_in_as(user) - end + post session_path, params: { email_address: user.email_address, password: "secret123456" } cookie = cookies.get_cookie "session_token" assert_not_nil cookie, "Expected session_token cookie to be set after sign in" From 2d327e6294193073d11233be2915bd9976b65f32 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 2 Oct 2025 14:46:23 -0400 Subject: [PATCH 39/47] Render a helpful menu of tenants temporarily --- app/controllers/concerns/authentication.rb | 4 +++- app/views/sessions/login_menu.html.erb | 15 +++++++++++++++ .../controllers/controller_authentication_test.rb | 3 ++- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 app/views/sessions/login_menu.html.erb diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb index 89871a9f5..2ede3c108 100644 --- a/app/controllers/concerns/authentication.rb +++ b/app/controllers/concerns/authentication.rb @@ -37,7 +37,9 @@ module Authentication end def require_tenant - ApplicationRecord.current_tenant.present? || request_authentication + unless ApplicationRecord.current_tenant.present? + render "sessions/login_menu" + end end def require_authentication diff --git a/app/views/sessions/login_menu.html.erb b/app/views/sessions/login_menu.html.erb new file mode 100644 index 000000000..e8c78fc61 --- /dev/null +++ b/app/views/sessions/login_menu.html.erb @@ -0,0 +1,15 @@ +<% cache ApplicationRecord.tenants do %> +

We're migrating Fizzy authentication

+

+ While we migrate Fizzy away from Launchpad and 37id, please login at one of the following URLs: + +

    + <% ApplicationRecord.with_each_tenant do |tenant| %> +
  • <%= link_to Account.sole.name, root_url(script_name: Account.sole.slug) %>
  • + <% end %> +
+

+

+ Mike should have sent you your temporary password by now. If you haven't received it, please ping him! +

+<% end %> diff --git a/test/controllers/controller_authentication_test.rb b/test/controllers/controller_authentication_test.rb index 02b729089..72e0df995 100644 --- a/test/controllers/controller_authentication_test.rb +++ b/test/controllers/controller_authentication_test.rb @@ -6,7 +6,8 @@ class ControllerAuthenticationTest < ActionDispatch::IntegrationTest get cards_path - assert_redirected_to new_session_path + assert_response :success + assert_dom "h2", text: "We're migrating Fizzy authentication" end test "access with an account slug but no session redirects to new session" do From 8db9862eb15a588a241adb838aa5b80ed33eeb83 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 2 Oct 2025 15:36:13 -0500 Subject: [PATCH 40/47] Use hide avatars from tabindex on preview --- app/views/cards/display/_preview.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/cards/display/_preview.html.erb b/app/views/cards/display/_preview.html.erb index 3f3b4b139..ce8f3d026 100644 --- a/app/views/cards/display/_preview.html.erb +++ b/app/views/cards/display/_preview.html.erb @@ -27,7 +27,7 @@
- <%= render "cards/display/preview/meta", card: card %> + <%= render "cards/display/preview/meta", card: card, preview: true %> <%= render "cards/display/common/background", card: card %>
From 67ab3d307edd5a937bc04e2f52dafd5e4be811a8 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Thu, 2 Oct 2025 15:37:07 -0500 Subject: [PATCH 41/47] Visually hide the pagination indicator It was appearing when the page first loads and anytime you changed the filter --- app/helpers/pagination_helper.rb | 4 ++-- app/views/users/_activity_timeline.html.erb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb index ee665e98a..6c67400c7 100644 --- a/app/helpers/pagination_helper.rb +++ b/app/helpers/pagination_helper.rb @@ -54,8 +54,8 @@ module PaginationHelper def day_timeline_pagination_link(day_timeline, filter) if day_timeline.next_day - link_to "Load more...", events_days_path(day: day_timeline.next_day.strftime("%Y-%m-%d"), **filter.as_params), - data: { frame: day_timeline_pagination_frame_id_for(day_timeline.next_day), pagination_target: "paginationLink" } + link_to "Load more…", events_days_path(day: day_timeline.next_day.strftime("%Y-%m-%d"), **filter.as_params), + class: "txt-reversed", data: { frame: day_timeline_pagination_frame_id_for(day_timeline.next_day), pagination_target: "paginationLink" } end end diff --git a/app/views/users/_activity_timeline.html.erb b/app/views/users/_activity_timeline.html.erb index 1c3b84d56..cbe5ce302 100644 --- a/app/views/users/_activity_timeline.html.erb +++ b/app/views/users/_activity_timeline.html.erb @@ -3,8 +3,8 @@ <%= render "events/day", day_timeline: day_timeline %> <% if day_timeline.next_day %> - <%= link_to "Load more...", user_path(user, day: day_timeline.next_day.strftime("%Y-%m-%d"), **filter.as_params), - data: { frame: day_timeline_pagination_frame_id_for(day_timeline.next_day), pagination_target: "paginationLink" } %> + <%= link_to "Load more…", user_path(user, day: day_timeline.next_day.strftime("%Y-%m-%d"), **filter.as_params), + class: "txt-reversed", data: { frame: day_timeline_pagination_frame_id_for(day_timeline.next_day), pagination_target: "paginationLink" } %> <% end %> <% end %>
From 3cc0fd8c1f14a5e5b29b4cf750ebfc340f255255 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 2 Oct 2025 13:46:04 -0400 Subject: [PATCH 42/47] Add a script to generate unique passwords per email address as part of the migration away from 37id/Launchpad. --- .../202510-migrate-to-local-authentication.rb | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 script/migrations/202510-migrate-to-local-authentication.rb diff --git a/script/migrations/202510-migrate-to-local-authentication.rb b/script/migrations/202510-migrate-to-local-authentication.rb new file mode 100755 index 000000000..cd8788092 --- /dev/null +++ b/script/migrations/202510-migrate-to-local-authentication.rb @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby + +# +# set up a temporary password for all users +# while we migrate away from Launchpad and 37id. +# +# the password is unguessable but generated based on the email address and a seed, +# so each user will have the same password across all tenants. +# +require_relative "../../config/environment" + +seed = ARGV[0] || "temporary-password-seed" +puts "Using seed: #{seed.inspect}" + +TENANTS = Hash.new +USERS = Hash.new { |h, k| h[k] = Hash.new } + +ApplicationRecord.with_each_tenant do |tenant| + TENANTS[tenant] = Account.sole.name + + User.find_each do |user| + putc "." + next if user.system? + + if user.external_user_id + suser = SignalId::User.find_by_id(user.external_user_id) + if suser && suser.email_address != user.email_address + puts "\nWarning: fixing email address for user #{user.id} in tenant #{tenant}:" + puts " local: #{user.email_address}" + puts " signal: #{suser.email_address}" + user.update! email_address: suser.email_address + end + end + + password = Digest::SHA256.hexdigest("#{seed}-#{user.email_address}")[0..16] + user.update! password: password + + USERS[user.email_address][tenant] = password + end +end + +puts + +USERS.each do |email, hash| + puts "\n#{email}:" + puts " password: #{hash.first.last}" + puts " fizzies:" + hash.each do |tenant, _| + url = Rails.application.routes.url_helpers.root_url(Rails.application.config.action_controller.default_url_options.merge(script_name: "/#{tenant}")) + + puts " #{TENANTS[tenant]}: #{url}" + end +end From 320558919c768c729816709124e80fa805d934ca Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 2 Oct 2025 15:18:11 -0400 Subject: [PATCH 43/47] Rename LOCAL_AUTHENTICATION to OSS_CONFIG --- Gemfile | 2 +- bin/dev | 4 +- bin/rails | 2 +- bin/setup | 2 +- config/ci.rb | 8 +- config/initializers/authentication.rb | 2 +- config/routes.rb | 2 +- db/seeds.rb | 4 +- lib/bootstrap.rb | 4 +- script/create-local-user.rb | 33 -------- test/models/period_highlights_test.rb | 35 ++++---- test/models/user/highlights_test.rb | 111 +++++++++++++------------- test/test_helper.rb | 2 +- 13 files changed, 86 insertions(+), 125 deletions(-) delete mode 100755 script/create-local-user.rb diff --git a/Gemfile b/Gemfile index c72bd8326..9296044c0 100644 --- a/Gemfile +++ b/Gemfile @@ -71,7 +71,7 @@ group :test do end require_relative "lib/bootstrap" -unless Bootstrap.local_authentication? +unless Bootstrap.oss_config? eval_gemfile "gems/fizzy-saas/Gemfile" gem "fizzy-saas", path: "gems/fizzy-saas" end diff --git a/bin/dev b/bin/dev index 9fe70d369..cc4e4ce62 100755 --- a/bin/dev +++ b/bin/dev @@ -14,8 +14,8 @@ if [ -f tmp/solid-queue.txt ]; then export SOLID_QUEUE_IN_PUMA=1 fi -if [ -f tmp/local-auth.txt ]; then - export LOCAL_AUTHENTICATION=1 +if [ -f tmp/oss-config.txt ]; then + export OSS_CONFIG=1 fi exec ./bin/rails server -p 3006 diff --git a/bin/rails b/bin/rails index 61fa81667..ab22dd770 100755 --- a/bin/rails +++ b/bin/rails @@ -1,6 +1,6 @@ #!/usr/bin/env ruby require_relative "../lib/bootstrap" -if !Bootstrap.local_authentication? +if !Bootstrap.oss_config? # default from rails/test_unit/runner.rb but adding the saas gem test files ENV["DEFAULT_TEST"] = "{gems/fizzy-saas/,}test/**/*_test.rb" ENV["DEFAULT_TEST_EXCLUDE"] = "{gems/fizzy-saas/,}test/{system,dummy,fixtures}/**/*_test.rb" diff --git a/bin/setup b/bin/setup index 10f30e733..329858c5b 100755 --- a/bin/setup +++ b/bin/setup @@ -11,7 +11,7 @@ system("bundle config set --local auto_install true") system("bundle check") || system!("bundle install") require_relative "../lib/bootstrap" -load File.expand_path("../gems/fizzy-saas/bin/setup", __dir__) unless Bootstrap.local_authentication? +load File.expand_path("../gems/fizzy-saas/bin/setup", __dir__) unless Bootstrap.oss_config? puts "\n== Preparing database ==" if ARGV.include?("--reset") diff --git a/config/ci.rb b/config/ci.rb index 121deb06a..304d4cd49 100644 --- a/config/ci.rb +++ b/config/ci.rb @@ -9,10 +9,10 @@ CI.run do step "Security: Importmap audit", "bin/importmap audit" step "Security: Brakeman audit", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error" - step "Tests: Rails with 37id auth", "bin/rails test" - step "Tests: Rails with local auth", "LOCAL_AUTHENTICATION=1 bin/rails test" - step "Tests: 37id", "bin/rails 37id:test:units" - step "Tests: System", "bin/rails test:system" + step "Tests: Rails: SaaS config", "bin/rails test" + step "Tests: Rails: OSS config", "OSS_CONFIG=1 bin/rails test" + step "Tests: 37id", "bin/rails 37id:test:units" + step "Tests: System", "bin/rails test:system" if success? step "Signoff: All systems go. Ready for merge and deploy.", "gh signoff" diff --git a/config/initializers/authentication.rb b/config/initializers/authentication.rb index 1ed66cd86..5556a9661 100644 --- a/config/initializers/authentication.rb +++ b/config/initializers/authentication.rb @@ -1,2 +1,2 @@ require "bootstrap" -Rails.application.config.x.local_authentication = Bootstrap.local_authentication? +Rails.application.config.x.oss_config = Bootstrap.oss_config? diff --git a/config/routes.rb b/config/routes.rb index 2c6c8e761..5e187e3e1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -221,7 +221,7 @@ Rails.application.routes.draw do root "events#index" - unless Rails.application.config.x.local_authentication + unless Rails.application.config.x.oss_config mount Fizzy::Saas::Engine, at: "/", as: "saas" end diff --git a/db/seeds.rb b/db/seeds.rb index 98954991d..32532d258 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -13,7 +13,7 @@ end def create_tenant(signal_account_name, bare: false) if bare tenant_id = Digest::SHA256.hexdigest(signal_account_name)[0..8].to_i(16) - elsif Rails.application.config.x.local_authentication + elsif Rails.application.config.x.oss_config tenant_id = ActiveRecord::FixtureSet.identify signal_account_name else signal_account = SignalId::Account.find_by_product_and_name!("fizzy", signal_account_name) @@ -22,7 +22,7 @@ def create_tenant(signal_account_name, bare: false) ApplicationRecord.destroy_tenant tenant_id ApplicationRecord.create_tenant(tenant_id) do - account = if bare || Rails.application.config.x.local_authentication + account = if bare || Rails.application.config.x.oss_config Account.create(name: signal_account_name, tenant_id: tenant_id).tap do User.create!( name: "David Heinemeier Hansson", diff --git a/lib/bootstrap.rb b/lib/bootstrap.rb index 1cfe4042c..6d52f3f49 100644 --- a/lib/bootstrap.rb +++ b/lib/bootstrap.rb @@ -1,5 +1,5 @@ module Bootstrap - def self.local_authentication? - ENV.fetch("LOCAL_AUTHENTICATION", "") != "" || !File.directory?(File.expand_path("../gems/fizzy-saas", __dir__)) + def self.oss_config? + ENV.fetch("OSS_CONFIG", "") != "" || !File.directory?(File.expand_path("../gems/fizzy-saas", __dir__)) end end diff --git a/script/create-local-user.rb b/script/create-local-user.rb deleted file mode 100755 index 7b303774f..000000000 --- a/script/create-local-user.rb +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env ruby -# -# This script creates a new user that can be logged into via LOCAL_AUTHENTICATION, avoiding Launchpad/37id integration. -# In order to login as this user, you must set the `LOCAL_AUTHENTICATION` environment variable when running the Rails server: -# -# LOCAL_AUTHENTICATION=1 bin/dev -# - -require_relative "../config/environment" - -unless Rails.env.development? - puts "ERROR: This script is intended to be run in development mode only." - exit 1 -end - -if ARGV.length < 2 - puts "Usage: #{$0} " - exit 1 -end - -email_address = ARGV[0] -tenant = ARGV[1] - -ApplicationRecord.with_tenant(tenant) do - user = User.create!( - name: email_address.split("@").first, - email_address: email_address, - password: "secret123456" - ) - - puts "Created: " - pp user -end diff --git a/test/models/period_highlights_test.rb b/test/models/period_highlights_test.rb index cf7627201..5bcfca99a 100644 --- a/test/models/period_highlights_test.rb +++ b/test/models/period_highlights_test.rb @@ -1,30 +1,27 @@ require "test_helper" class PeriodHighlightTest < ActiveSupport::TestCase - # Skipping when locally authenticating because the VCR cassettes would need to be re-recorded. - unless Rails.application.config.x.local_authentication - include VcrTestHelper + include VcrTestHelper - setup do - @user = users(:david) + setup do + @user = users(:david) + end + + test "generate period highlights" do + period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do + PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) end - test "generate period highlights" do - period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do - PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) - end + assert_match /logo/i, period_highlights.to_html + end - assert_match /logo/i, period_highlights.to_html + test "don't generate highlights for existing periods" do + new_period_highlights = PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) + + existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do + PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) end - test "don't generate highlights for existing periods" do - new_period_highlights = PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) - - existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do - PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) - end - - assert_equal new_period_highlights, existing_period_highlights - end + assert_equal new_period_highlights, existing_period_highlights end end diff --git a/test/models/user/highlights_test.rb b/test/models/user/highlights_test.rb index 0344d02b8..7123f3bc2 100644 --- a/test/models/user/highlights_test.rb +++ b/test/models/user/highlights_test.rb @@ -1,67 +1,64 @@ require "test_helper" class User::HighlightsTest < ActiveSupport::TestCase - # Skipping when locally authenticating because the VCR cassettes would need to be re-recorded. - unless Rails.application.config.x.local_authentication - include VcrTestHelper + include VcrTestHelper - setup do - @user = users(:david) - travel_to 1.week.ago + 2.days - Current.session = sessions(:david) - end + setup do + @user = users(:david) + travel_to 1.week.ago + 2.days + Current.session = sessions(:david) + end - test "generate weekly highlights" do - stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do - period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do - @user.generate_weekly_highlights - end - - assert_match /logo/i, period_highlights.to_html - end - end - - test "don't generate highlights for existing periods" do - stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do - new_period_highlights = @user.generate_weekly_highlights - assert_not_nil new_period_highlights - - existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do - @user.generate_weekly_highlights - end - - assert_equal new_period_highlights, existing_period_highlights - end - end - - test "periods respect user timezone for week boundaries" do - @user.settings.update!(timezone_name: "America/New_York") - - # Sunday Jan 7, 2024 at 2am EST (7am UTC) - this is Sunday in NYC - sunday_in_nyc = Time.zone.parse("2024-01-07 07:00:00 UTC") - - # Saturday Jan 6, 2024 at 11pm EST (Jan 7 4am UTC) - still Saturday in NYC but Sunday in UTC - saturday_in_nyc = Time.zone.parse("2024-01-07 04:00:00 UTC") - - # Event on Saturday evening in NYC (but Sunday in UTC) - saturday_event = travel_to(saturday_in_nyc) { cards(:logo).track_event("Saturday event") } - - # Events throughout the week starting Sunday in NYC - 7.times do |i| - travel_to(sunday_in_nyc + i.days) { cards(:logo).track_event("Event #{i}") } + test "generate weekly highlights" do + stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do + period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do + @user.generate_weekly_highlights end - wednesday = sunday_in_nyc + 3.days - - # The period should start at Sunday in NYC timezone - period_start = wednesday.in_time_zone("America/New_York").beginning_of_week(:sunday) - period = PeriodHighlights::Period.new(@user.collections, starts_at: period_start, duration: 1.week) - - # The Saturday event should NOT be included (it's in the previous week in NYC time) - assert_not_includes period.events, saturday_event - - # Should include 7 events from the current week - assert_equal 7, period.events.count + assert_match /logo/i, period_highlights.to_html end end + + test "don't generate highlights for existing periods" do + stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do + new_period_highlights = @user.generate_weekly_highlights + assert_not_nil new_period_highlights + + existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do + @user.generate_weekly_highlights + end + + assert_equal new_period_highlights, existing_period_highlights + end + end + + test "periods respect user timezone for week boundaries" do + @user.settings.update!(timezone_name: "America/New_York") + + # Sunday Jan 7, 2024 at 2am EST (7am UTC) - this is Sunday in NYC + sunday_in_nyc = Time.zone.parse("2024-01-07 07:00:00 UTC") + + # Saturday Jan 6, 2024 at 11pm EST (Jan 7 4am UTC) - still Saturday in NYC but Sunday in UTC + saturday_in_nyc = Time.zone.parse("2024-01-07 04:00:00 UTC") + + # Event on Saturday evening in NYC (but Sunday in UTC) + saturday_event = travel_to(saturday_in_nyc) { cards(:logo).track_event("Saturday event") } + + # Events throughout the week starting Sunday in NYC + 7.times do |i| + travel_to(sunday_in_nyc + i.days) { cards(:logo).track_event("Event #{i}") } + end + + wednesday = sunday_in_nyc + 3.days + + # The period should start at Sunday in NYC timezone + period_start = wednesday.in_time_zone("America/New_York").beginning_of_week(:sunday) + period = PeriodHighlights::Period.new(@user.collections, starts_at: period_start, duration: 1.week) + + # The Saturday event should NOT be included (it's in the previous week in NYC time) + assert_not_includes period.events, saturday_event + + # Should include 7 events from the current week + assert_equal 7, period.events.count + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index a71063f10..fbb2b1149 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -65,6 +65,6 @@ RubyLLM.configure do |config| config.openai_api_key ||= "DUMMY-TEST-KEY" # Run tests with VCR without having to configure OpenAI API key locally. end -unless Rails.application.config.x.local_authentication +unless Rails.application.config.x.oss_config load File.expand_path("../gems/fizzy-saas/test/test_helper.rb", __dir__) end From fcd3c178ce01af357295d85d7b17594d4c7849d0 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 2 Oct 2025 16:20:09 -0400 Subject: [PATCH 44/47] Account#slug uses the virtual "tenant" attribute --- app/models/account.rb | 2 +- bin/dev | 6 ++---- test/models/account_test.rb | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 950c0a124..c29f61005 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -4,7 +4,7 @@ class Account < ApplicationRecord has_many_attached :uploads def slug - "/#{tenant_id}" + "/#{tenant}" end def setup_basic_template diff --git a/bin/dev b/bin/dev index cc4e4ce62..1c77800e3 100755 --- a/bin/dev +++ b/bin/dev @@ -1,12 +1,10 @@ #!/usr/bin/env sh -echo "Login with david@37signals.com / secret123456 to:" bin/rails runner - < Date: Thu, 2 Oct 2025 15:56:21 -0500 Subject: [PATCH 45/47] Move video captions below content --- app/assets/stylesheets/rich-text-content.css | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/rich-text-content.css b/app/assets/stylesheets/rich-text-content.css index 18641da1a..873ad8cd9 100644 --- a/app/assets/stylesheets/rich-text-content.css +++ b/app/assets/stylesheets/rich-text-content.css @@ -248,7 +248,7 @@ } .attachment--file { - --attachment-icon-color: var(--lexxy-color-text-subtle); + --attachment-icon-color: var(--color-ink-medium); align-items: center; display: flex; @@ -266,6 +266,15 @@ color: var(--color-ink); font-weight: bold; } + + /* Video attachments don't have an identifiable class, but we need to + * make sure the caption is always below the video */ + &:has(video) { + .attachment__caption { + flex: none; + inline-size: 100%; + } + } } .attachment--psd, From c785d1105d42455412f81f03365a9af9a651ee1f Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 2 Oct 2025 16:10:22 -0500 Subject: [PATCH 46/47] Only reverse Watching button if icon_only --- app/helpers/accesses_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/accesses_helper.rb b/app/helpers/accesses_helper.rb index 24a3fdbec..c446dc5a2 100644 --- a/app/helpers/accesses_helper.rb +++ b/app/helpers/accesses_helper.rb @@ -50,7 +50,7 @@ module AccessesHelper collection_involvement_path(collection), method: :put, aria: { labelledby: involvement_label_id }, - class: class_names("btn", { "btn--reversed": access.involvement == "watching" }), + class: class_names("btn", { "btn--reversed": access.involvement == "watching" && icon_only }), params: { show_watchers: show_watchers, involvement: next_involvement(access.involvement), icon_only: icon_only } ) do safe_join([ From 73dd372ca9cc56c111606c1ca81aaf770c87481f Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 2 Oct 2025 16:25:38 -0500 Subject: [PATCH 47/47] Remove overflow from columns --- app/assets/stylesheets/card-columns.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index f4ff6ac05..1a50f110f 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -73,10 +73,6 @@ z-index: 3; } - &:not(.cards--considering, .is-collapsed, .cards--grid) { - overflow: clip; - } - &.is-collapsed { border-radius: calc(var(--column-width-collapsed) / 2); flex: 0 0 var(--column-width-collapsed);