From 53cb8055dfb1399f352af06d5fbaf4c31d976f24 Mon Sep 17 00:00:00 2001 From: Hampton Lintorn-Catlin Date: Mon, 1 Jun 2026 15:31:58 -0500 Subject: [PATCH 1/3] Homepage card cleanup: density, grouped My Plans, content preview (COPLAN-22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes plan cards scannable so the homepage works at scale. * Default scope is now "Mine" — the homepage opens to your own plans grouped by status (developing → live → considering → brainstorm → abandoned). "All" is one click away. `scope` is validated against an allow-list to leave room for a future "team" scope. * New `Plan.prioritized_by_status` scope (DB-side CASE via `sanitize_sql_array`) keeps grouping in SQL — no Ruby-side sorts. * Cards show a 2-line markdown-stripped content preview via a new `PlansHelper#plan_content_preview` helper. The card reads `plan.try(:summary).presence` first so COPLAN-24's AI-summary column swaps in with no card changes when it lands. * Density pass: collapsed three filter strips into one tight toolbar, dropped the redundant "Plans" h1, smaller card padding, tags moved inline with meta. Result: at least 7 cards above the fold @ 1280x800. * Card meta separators use a `+ ::before` pseudo-element instead of per-item separator spans (cleaner DOM, per code-review feedback). * Eager-loads `:current_plan_version` to avoid an N+1 from the new preview helper. * Specs: helper unit specs + request specs for default scope, grouping, content preview, and scope=all parity. Adds factory traits for :developing, :live, :abandoned. Amp-Thread-ID: https://ampcode.com/threads/T-019e84ca-1a7e-7589-bc94-64454a438f90 Co-authored-by: Amp --- .../assets/stylesheets/coplan/application.css | 94 +++++++++++++++++-- .../controllers/coplan/plans_controller.rb | 34 +++++-- engine/app/helpers/coplan/plans_helper.rb | 18 ++++ engine/app/models/coplan/plan.rb | 13 +++ .../views/coplan/plans/_plan_page.html.erb | 49 +++++++--- engine/app/views/coplan/plans/index.html.erb | 70 ++++++++------ spec/factories/plans.rb | 12 +++ spec/helpers/plans_helper_spec.rb | 37 ++++++++ spec/requests/plans_spec.rb | 62 +++++++++++- 9 files changed, 330 insertions(+), 59 deletions(-) create mode 100644 engine/app/helpers/coplan/plans_helper.rb create mode 100644 spec/helpers/plans_helper_spec.rb diff --git a/engine/app/assets/stylesheets/coplan/application.css b/engine/app/assets/stylesheets/coplan/application.css index ff43c8a..2a275f5 100644 --- a/engine/app/assets/stylesheets/coplan/application.css +++ b/engine/app/assets/stylesheets/coplan/application.css @@ -705,7 +705,7 @@ img.avatar { text-align: left; } -/* Status filters */ +/* Status filters (used on the notifications index) */ .status-filters { display: flex; gap: var(--space-sm); @@ -713,8 +713,34 @@ img.avatar { flex-wrap: wrap; } +/* Plans toolbar (compact filter bar at top of index) */ +.plans-toolbar { + display: flex; + flex-direction: column; + gap: var(--space-xs); + margin-bottom: var(--space-md); +} + +.plans-toolbar__row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-xs); +} + +.plans-toolbar__row--secondary .status-filter { + font-size: 0.7rem; +} + +.plans-toolbar__divider { + width: 1px; + height: 16px; + background: var(--color-border); + margin: 0 var(--space-xs); +} + .status-filter { - padding: var(--space-xs) var(--space-md); + padding: 2px var(--space-sm); font-size: var(--text-sm); font-weight: 500; border-radius: 9999px; @@ -722,6 +748,7 @@ img.avatar { border: 1px solid var(--color-border); text-decoration: none; transition: all 0.15s; + line-height: 1.5; } .status-filter:hover { @@ -739,35 +766,82 @@ img.avatar { .plans-list { display: flex; flex-direction: column; - gap: var(--space-md); + gap: var(--space-xs); +} + +.plans-list__item { + padding: var(--space-sm) var(--space-md); + display: flex; + flex-direction: column; + gap: 2px; +} + +.plans-list__section { + font-size: 0.7rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-text-muted); + margin: var(--space-sm) 0 2px; + padding-bottom: 2px; + border-bottom: 1px solid var(--color-border); +} + +.plans-list__section:first-child { + margin-top: 0; } .plans-list__header { display: flex; align-items: center; - gap: var(--space-sm); - margin-bottom: var(--space-xs); + flex-wrap: wrap; + gap: var(--space-xs); } .plans-list__title { - font-size: var(--text-lg); + font-size: var(--text-base); font-weight: 600; color: var(--color-text); + margin-right: var(--space-xs); } .plans-list__title:hover { color: var(--color-primary); } -.plans-list__tags { +.plans-list__summary { + margin: 0; + font-size: var(--text-sm); + color: var(--color-text-muted); + line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.plans-list__meta { display: flex; + align-items: center; flex-wrap: wrap; gap: var(--space-xs); - margin-top: var(--space-xs); } -.plans-list__meta { - margin-top: var(--space-xs); +.plans-list__meta-item { + display: inline-flex; + align-items: center; + gap: var(--space-xs); +} + +.plans-list__meta-item + .plans-list__meta-item::before { + content: "·"; + color: var(--color-text-muted); + opacity: 0.5; + margin-right: var(--space-xs); +} + +.plans-list__tags { + flex-wrap: wrap; } .active-filter { diff --git a/engine/app/controllers/coplan/plans_controller.rb b/engine/app/controllers/coplan/plans_controller.rb index 19be2eb..a2a290d 100644 --- a/engine/app/controllers/coplan/plans_controller.rb +++ b/engine/app/controllers/coplan/plans_controller.rb @@ -4,16 +4,30 @@ class PlansController < ApplicationController PER_PAGE = 20 + SCOPES = %w[mine all].freeze + DEFAULT_SCOPE = "mine".freeze + def index - plans = Plan.includes(:plan_type, :tags, :created_by_user) - .where.not(status: "brainstorm") - .or(Plan.where(created_by_user: current_user)) - .order(updated_at: :desc, id: :desc) + @scope = SCOPES.include?(params[:scope]) ? params[:scope] : DEFAULT_SCOPE + + plans = Plan.includes(:plan_type, :tags, :created_by_user, :current_plan_version) + + if @scope == "mine" + plans = plans.where(created_by_user: current_user) + else + plans = plans.where.not(status: "brainstorm") + .or(Plan.where(created_by_user: current_user)) + end + plans = plans.where(status: params[:status]) if params[:status].present? - plans = plans.where(created_by_user: current_user) if params[:scope] == "mine" plans = plans.where(plan_type_id: params[:plan_type]) if params[:plan_type].present? plans = plans.with_tag(params[:tag]) if params[:tag].present? + # Group "My Plans" by status (active → brainstorm) when not already filtered + # to a single status. The "All" view stays sorted by recency. + @grouped_by_status = @scope == "mine" && params[:status].blank? + plans = @grouped_by_status ? plans.prioritized_by_status : plans.order(updated_at: :desc, id: :desc) + @page = (params[:page] || 1).to_i @plans = plans.limit(PER_PAGE + 1).offset((@page - 1) * PER_PAGE) @has_next_page = @plans.size > PER_PAGE @@ -25,7 +39,15 @@ def index .count if turbo_frame_request? - render partial: "coplan/plans/plan_page", locals: { plans: @plans, plan_unread_counts: @plan_unread_counts, page: @page, has_next_page: @has_next_page }, layout: false + render partial: "coplan/plans/plan_page", + locals: { + plans: @plans, + plan_unread_counts: @plan_unread_counts, + page: @page, + has_next_page: @has_next_page, + grouped_by_status: @grouped_by_status, + }, + layout: false else @plan_types = PlanType.order(:name) @show_onboarding_banner = CoPlan.configuration.onboarding_banner.present? && diff --git a/engine/app/helpers/coplan/plans_helper.rb b/engine/app/helpers/coplan/plans_helper.rb new file mode 100644 index 0000000..0b751c7 --- /dev/null +++ b/engine/app/helpers/coplan/plans_helper.rb @@ -0,0 +1,18 @@ +module CoPlan + module PlansHelper + include MarkdownHelper + + # Short preview of the plan's content for cards on the index page. + # Once an AI summary column lands (COPLAN-24), the card view prefers + # `plan.summary` and falls back to this helper. + def plan_content_preview(plan, limit: 200) + content = plan.current_content + return nil if content.blank? + + plain = markdown_to_plain_text(content) + return nil if plain.blank? + + truncate(plain, length: limit, omission: "…", separator: " ") + end + end +end diff --git a/engine/app/models/coplan/plan.rb b/engine/app/models/coplan/plan.rb index 8265f47..2c40506 100644 --- a/engine/app/models/coplan/plan.rb +++ b/engine/app/models/coplan/plan.rb @@ -2,6 +2,19 @@ module CoPlan class Plan < ApplicationRecord STATUSES = %w[brainstorm considering developing live abandoned].freeze + # Order used when grouping "My Plans" on the index: active work first, + # brainstorms next, abandoned last. + STATUS_PRIORITY = %w[developing live considering brainstorm abandoned].freeze + + # Order plans by STATUS_PRIORITY, then most-recently-updated within each group. + scope :prioritized_by_status, -> { + whens = STATUS_PRIORITY.each_with_index.map { |status, i| + sanitize_sql_array(["WHEN status = ? THEN ?", status, i]) + }.join(" ") + order(Arel.sql("CASE #{whens} ELSE #{STATUS_PRIORITY.length} END")) + .order(updated_at: :desc, id: :desc) + } + belongs_to :created_by_user, class_name: "CoPlan::User" belongs_to :current_plan_version, class_name: "PlanVersion", optional: true belongs_to :plan_type, optional: true diff --git a/engine/app/views/coplan/plans/_plan_page.html.erb b/engine/app/views/coplan/plans/_plan_page.html.erb index a976b6e..84ab7a3 100644 --- a/engine/app/views/coplan/plans/_plan_page.html.erb +++ b/engine/app/views/coplan/plans/_plan_page.html.erb @@ -1,7 +1,17 @@ <%= turbo_frame_tag "plans-page-#{page}" do %> + <% previous_status = nil %> <% plans.each do |plan| %> -
-
+ <% if grouped_by_status && plan.status != previous_status %> +

+ <%= plan.status.titleize %> +

+ <% previous_status = plan.status %> + <% end %> + + <% summary = plan.try(:summary).presence || plan_content_preview(plan) %> + +
+
<%= link_to plan.title, plan_path(plan), class: "plans-list__title", data: { turbo_frame: "_top" } %> <%= plan.status %> <% if plan.plan_type %> @@ -11,22 +21,37 @@ <% if plan_unread > 0 %> <%= plan_unread %> <% end %> -
- <% if plan.tags.any? %> -
- <% plan.tags.each do |tag| %> - <%= link_to tag.name, plans_path(params.permit(:scope, :status, :plan_type).merge(tag: tag.name)), class: "badge badge--tag #{'badge--tag-active' if params[:tag] == tag.name}", data: { turbo_frame: "_top" } %> - <% end %> -
+ + + <% if summary.present? %> +

<%= summary %>

<% end %> +
- <%= user_avatar(plan.created_by_user) %> <%= plan.created_by_user.name %> · v<%= plan.current_revision %> · updated <%= time_ago_in_words(plan.updated_at) %> ago + + <%= user_avatar(plan.created_by_user) %> + <%= plan.created_by_user.name %> + + v<%= plan.current_revision %> + updated <%= time_ago_in_words(plan.updated_at) %> ago + <% if plan.tags.any? %> + + <% plan.tags.each do |tag| %> + <%= link_to tag.name, + plans_path(params.permit(:scope, :status, :plan_type).merge(tag: tag.name)), + class: "badge badge--tag #{'badge--tag-active' if params[:tag] == tag.name}", + data: { turbo_frame: "_top" } %> + <% end %> + + <% end %>
-
+ <% end %> <% if has_next_page %> - <%= turbo_frame_tag "plans-page-#{page + 1}", src: plans_path(params.permit(:scope, :status, :plan_type, :tag).merge(page: page + 1)), loading: :lazy do %> + <%= turbo_frame_tag "plans-page-#{page + 1}", + src: plans_path(params.permit(:scope, :status, :plan_type, :tag).merge(page: page + 1)), + loading: :lazy do %>
Loading more plans…
<% end %> <% end %> diff --git a/engine/app/views/coplan/plans/index.html.erb b/engine/app/views/coplan/plans/index.html.erb index c022475..58a34f7 100644 --- a/engine/app/views/coplan/plans/index.html.erb +++ b/engine/app/views/coplan/plans/index.html.erb @@ -1,41 +1,53 @@ - +
+
+ <%= link_to "Mine", plans_path(params.permit(:status, :plan_type, :tag)), + class: "status-filter #{'status-filter--active' if @scope == 'mine'}" %> + <%= link_to "All", plans_path(params.permit(:status, :plan_type, :tag).merge(scope: "all")), + class: "status-filter #{'status-filter--active' if @scope == 'all'}" %> + + <%= link_to "Any status", plans_path(params.permit(:scope, :plan_type, :tag)), + class: "status-filter #{'status-filter--active' if params[:status].blank?}" %> + <% CoPlan::Plan::STATUSES.each do |status| %> + <%= link_to status.titleize, plans_path(params.permit(:scope, :plan_type, :tag).merge(status: status)), + class: "status-filter status-filter--#{status} #{'status-filter--active' if params[:status] == status}" %> + <% end %> +
-
- <%= link_to "All Plans", plans_path(params.permit(:status, :tag)), class: "status-filter #{'status-filter--active' if params[:scope].blank?}" %> - <%= link_to "My Plans", plans_path(params.permit(:status, :tag).merge(scope: "mine")), class: "status-filter #{'status-filter--active' if params[:scope] == 'mine'}" %> -
+ <% if @plan_types.any? %> +
+ <%= link_to "Any type", plans_path(params.permit(:scope, :status, :tag)), + class: "status-filter #{'status-filter--active' if params[:plan_type].blank?}" %> + <% @plan_types.each do |pt| %> + <%= link_to pt.name, plans_path(params.permit(:scope, :status, :tag).merge(plan_type: pt.id)), + class: "status-filter #{'status-filter--active' if params[:plan_type] == pt.id}" %> + <% end %> +
+ <% end %> -
- <%= link_to "All", plans_path(params.permit(:scope, :plan_type, :tag)), class: "status-filter #{'status-filter--active' if params[:status].blank?}" %> - <% CoPlan::Plan::STATUSES.each do |status| %> - <%= link_to status.titleize, plans_path(params.permit(:scope, :plan_type, :tag).merge(status: status)), class: "status-filter status-filter--#{status} #{'status-filter--active' if params[:status] == status}" %> + <% if params[:tag].present? %> +
+ Filtered by tag: <%= params[:tag] %> + <%= link_to "✕ Clear", plans_path(params.permit(:scope, :status, :plan_type)), class: "active-filter__clear" %> +
<% end %>
-<% if @plan_types.any? %> -
- <%= link_to "All Types", plans_path(params.permit(:scope, :status, :tag)), class: "status-filter #{'status-filter--active' if params[:plan_type].blank?}" %> - <% @plan_types.each do |pt| %> - <%= link_to pt.name, plans_path(params.permit(:scope, :status, :tag).merge(plan_type: pt.id)), class: "status-filter #{'status-filter--active' if params[:plan_type] == pt.id}" %> - <% end %> -
-<% end %> - -<% if params[:tag].present? %> -
- Filtered by tag: <%= params[:tag] %> - <%= link_to "✕ Clear", plans_path(params.permit(:scope, :status, :plan_type)), class: "active-filter__clear" %> -
-<% end %> - <% if @plans.any? %>
- <%= render partial: "coplan/plans/plan_page", locals: { plans: @plans, plan_unread_counts: @plan_unread_counts, page: @page, has_next_page: @has_next_page } %> + <%= render partial: "coplan/plans/plan_page", locals: { + plans: @plans, + plan_unread_counts: @plan_unread_counts, + page: @page, + has_next_page: @has_next_page, + grouped_by_status: @grouped_by_status, + } %>
<% else %>
-

No plans yet. Plans are created via the API.

+ <% if @scope == "mine" %> +

You haven't created any plans yet. Plans are created via the API.

+ <% else %> +

No plans yet. Plans are created via the API.

+ <% end %>
<% end %> diff --git a/spec/factories/plans.rb b/spec/factories/plans.rb index c3ffb0e..fa64024 100644 --- a/spec/factories/plans.rb +++ b/spec/factories/plans.rb @@ -20,5 +20,17 @@ trait :brainstorm do status { "brainstorm" } end + + trait :developing do + status { "developing" } + end + + trait :live do + status { "live" } + end + + trait :abandoned do + status { "abandoned" } + end end end diff --git a/spec/helpers/plans_helper_spec.rb b/spec/helpers/plans_helper_spec.rb new file mode 100644 index 0000000..0857b41 --- /dev/null +++ b/spec/helpers/plans_helper_spec.rb @@ -0,0 +1,37 @@ +require "rails_helper" + +RSpec.describe CoPlan::PlansHelper, type: :helper do + describe "#plan_content_preview" do + let(:plan) { create(:plan, :considering) } + + it "strips markdown formatting and returns a plain-text preview" do + plan.current_plan_version.update!( + content_markdown: "# Heading\n\nA **bold** intro with [a link](https://example.com)." + ) + preview = helper.plan_content_preview(plan) + expect(preview).to include("Heading") + expect(preview).to include("A bold intro with a link") + expect(preview).not_to include("**") + expect(preview).not_to include("](") + end + + it "truncates to the requested limit" do + plan.current_plan_version.update!(content_markdown: "word " * 100) + preview = helper.plan_content_preview(plan, limit: 40) + expect(preview.length).to be <= 41 # 40 chars + ellipsis + expect(preview).to end_with("…") + end + + it "returns nil when the plan has no content" do + plan.current_plan_version.update_columns(content_markdown: "", content_sha256: Digest::SHA256.hexdigest("")) + plan.reload + expect(helper.plan_content_preview(plan)).to be_nil + end + + it "returns nil when the plan has no version" do + plan.update_columns(current_plan_version_id: nil) + plan.reload + expect(helper.plan_content_preview(plan)).to be_nil + end + end +end diff --git a/spec/requests/plans_spec.rb b/spec/requests/plans_spec.rb index 7233aad..2048924 100644 --- a/spec/requests/plans_spec.rb +++ b/spec/requests/plans_spec.rb @@ -139,10 +139,10 @@ expect(response).to redirect_to(plan_path(plan)) end - it "index hides other users brainstorm plans" do + it "index hides other users brainstorm plans on the All scope" do brainstorm_plan # alice's brainstorm sign_in_as(bob) - get plans_path + get plans_path(scope: "all") expect(response).to have_http_status(:success) expect(response.body).not_to include(brainstorm_plan.title) end @@ -154,6 +154,64 @@ expect(response.body).to include(brainstorm_plan.title) end + describe "default scope" do + it "defaults to 'mine' and hides other users' plans" do + plan # alice's + bobs_plan = create(:plan, :considering, created_by_user: bob, title: "Bobs Roadmap") + get plans_path + expect(response.body).to include(plan.title) + expect(response.body).not_to include(bobs_plan.title) + end + + it "scope=all shows everyone's published plans" do + plan # alice's + bobs_plan = create(:plan, :considering, created_by_user: bob, title: "Bobs Roadmap") + get plans_path(scope: "all") + expect(response.body).to include(plan.title) + expect(response.body).to include(bobs_plan.title) + end + + it "groups My Plans by status with section headers" do + create(:plan, :developing, created_by_user: alice, title: "Developing Plan") + create(:plan, :considering, created_by_user: alice, title: "Considering Plan") + create(:plan, :brainstorm, created_by_user: alice, title: "Brainstorm Plan") + get plans_path + expect(response.body).to include("plans-list__section") + # active work appears before brainstorm + expect(response.body.index("Developing Plan")).to be < response.body.index("Brainstorm Plan") + expect(response.body.index("Considering Plan")).to be < response.body.index("Brainstorm Plan") + end + + it "does not group when filtered to a single status" do + create(:plan, :developing, created_by_user: alice, title: "Developing Plan") + get plans_path(status: "developing") + expect(response.body).not_to include("plans-list__section") + expect(response.body).to include("Developing Plan") + end + + it "scope=all does not group by status" do + create(:plan, :developing, created_by_user: alice, title: "Developing Plan") + get plans_path(scope: "all") + expect(response.body).not_to include("plans-list__section") + end + end + + describe "content preview on cards" do + it "renders a markdown-stripped preview when there is no AI summary" do + plan.current_plan_version.update!(content_markdown: "# Heading\n\nThis is the **plan body** with [links](https://example.com).") + get plans_path + expect(response.body).to include("plans-list__summary") + expect(response.body).to include("This is the plan body with links") + expect(response.body).not_to include("**plan body**") + end + + it "omits the summary block when the plan has no content" do + plan.current_plan_version.update_columns(content_markdown: "", content_sha256: Digest::SHA256.hexdigest("")) + get plans_path + expect(response.body).not_to include("plans-list__summary") + end + end + it "can view brainstorm plan as non-author" do sign_in_as(bob) get plan_path(brainstorm_plan) From 37de6c2d66e44a38c775d63011ec165791cef609 Mon Sep 17 00:00:00 2001 From: Hampton Lintorn-Catlin Date: Mon, 1 Jun 2026 16:00:35 -0500 Subject: [PATCH 2/3] Polish: drop section underline, fix paginated header dup, breathe between cards * Remove border-bottom from .plans-list__section (Hampton: thin line felt out of place). * Pass previous page's last status through the Turbo Frame URL so the next page doesn't re-emit a section header when its first plan has the same status as the last one on the previous page. * Bump card-to-card gap to space-sm and card padding to a uniform space-md so the list reads less cramped. Amp-Thread-ID: https://ampcode.com/threads/T-019e84ca-1a7e-7589-bc94-64454a438f90 Co-authored-by: Amp --- engine/app/assets/stylesheets/coplan/application.css | 10 ++++------ engine/app/controllers/coplan/plans_controller.rb | 1 + engine/app/views/coplan/plans/_plan_page.html.erb | 6 ++++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/engine/app/assets/stylesheets/coplan/application.css b/engine/app/assets/stylesheets/coplan/application.css index 2a275f5..d97893f 100644 --- a/engine/app/assets/stylesheets/coplan/application.css +++ b/engine/app/assets/stylesheets/coplan/application.css @@ -766,14 +766,14 @@ img.avatar { .plans-list { display: flex; flex-direction: column; - gap: var(--space-xs); + gap: var(--space-sm); } .plans-list__item { - padding: var(--space-sm) var(--space-md); + padding: var(--space-md); display: flex; flex-direction: column; - gap: 2px; + gap: var(--space-xs); } .plans-list__section { @@ -782,9 +782,7 @@ img.avatar { text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-text-muted); - margin: var(--space-sm) 0 2px; - padding-bottom: 2px; - border-bottom: 1px solid var(--color-border); + margin: var(--space-sm) 0 0; } .plans-list__section:first-child { diff --git a/engine/app/controllers/coplan/plans_controller.rb b/engine/app/controllers/coplan/plans_controller.rb index a2a290d..ec98693 100644 --- a/engine/app/controllers/coplan/plans_controller.rb +++ b/engine/app/controllers/coplan/plans_controller.rb @@ -46,6 +46,7 @@ def index page: @page, has_next_page: @has_next_page, grouped_by_status: @grouped_by_status, + previous_status: params[:prev_status].presence, }, layout: false else diff --git a/engine/app/views/coplan/plans/_plan_page.html.erb b/engine/app/views/coplan/plans/_plan_page.html.erb index 84ab7a3..cfcd7a1 100644 --- a/engine/app/views/coplan/plans/_plan_page.html.erb +++ b/engine/app/views/coplan/plans/_plan_page.html.erb @@ -1,5 +1,5 @@ <%= turbo_frame_tag "plans-page-#{page}" do %> - <% previous_status = nil %> + <% previous_status = local_assigns.fetch(:previous_status, nil) %> <% plans.each do |plan| %> <% if grouped_by_status && plan.status != previous_status %>

@@ -49,8 +49,10 @@ <% end %> <% if has_next_page %> + <% next_page_params = params.permit(:scope, :status, :plan_type, :tag).merge(page: page + 1) %> + <% next_page_params[:prev_status] = plans.last.status if grouped_by_status && plans.any? %> <%= turbo_frame_tag "plans-page-#{page + 1}", - src: plans_path(params.permit(:scope, :status, :plan_type, :tag).merge(page: page + 1)), + src: plans_path(next_page_params), loading: :lazy do %>
Loading more plans…
<% end %> From a124a6521219751287df5e8af66090ae0703571a Mon Sep 17 00:00:00 2001 From: Hampton Lintorn-Catlin Date: Mon, 1 Jun 2026 16:03:55 -0500 Subject: [PATCH 3/3] Preserve all-scope on landing 'Browse all plans' link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new default scope (mine) silently hid published plans when users clicked the 'Browse all plans →' link on the welcome landing page. Pass scope: "all" explicitly to match the link's promise. Amp-Thread-ID: https://ampcode.com/threads/T-019e84ca-1a7e-7589-bc94-64454a438f90 Co-authored-by: Amp --- engine/app/views/coplan/welcome/_default_landing.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/app/views/coplan/welcome/_default_landing.html.erb b/engine/app/views/coplan/welcome/_default_landing.html.erb index c0a1619..533b376 100644 --- a/engine/app/views/coplan/welcome/_default_landing.html.erb +++ b/engine/app/views/coplan/welcome/_default_landing.html.erb @@ -63,7 +63,7 @@ <% end %>