Preserve navigation context when opening cards from filtered views#2565
Preserve navigation context when opening cards from filtered views#2565Sanyamjin wants to merge 3 commits intobasecamp:mainfrom
Conversation
|
@jeremy Could you please review this PR when you have a moment? Thanks! |
There was a problem hiding this comment.
Pull request overview
Adds stateless “return to where I came from” navigation when opening a card from filtered/alternate views, so the back link can return to the originating list instead of always returning to the board.
Changes:
- Appends
return_toandreturn_labelquery params to card links from card preview tiles. - Updates
link_back_to_boardto prefer a validatedreturn_to+return_label, falling back to the board path/name. - Introduces
page_title_for_returnto supply a label for the return context.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| app/views/cards/display/_preview.html.erb | Adds return_to/return_label to card links from preview tiles. |
| app/helpers/boards_helper.rb | Updates back-link helper to use validated return params (open-redirect mitigation). |
| app/helpers/application_helper.rb | Adds helper to provide a label for “return” navigation context. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <%= card_article_tag card, class: "card", draggable: draggable, data: card_data, tabindex: 0 do %> | ||
| <div class="flex flex-column flex-item-grow max-inline-size"> | ||
| <%= link_to card_path(card), draggable: false, class: "card__link", title: card_title_tag(card), data: { action: "dialog#close", turbo_frame: "_top" } do %> | ||
| <%= link_to card_path(card, return_to: request.fullpath, return_label: page_title_for_return), draggable: false, class: "card__link", title: card_title_tag(card), data: { action: "dialog#close", turbo_frame: "_top" } do %> |
There was a problem hiding this comment.
card_path(... return_to: request.fullpath ...) is rendered inside a collection-cached partial (see cards/display/_previews.html.erb uses cached: true) and is also rendered from Turbo Stream pagination (cards/previews#index). This will (1) cache and reuse a return_to from the first render across other pages/filters/users, and (2) set return_to to the pagination endpoint (e.g. /cards/previews?...) when cards are appended via Turbo Streams, breaking the back link (HTML navigation to that endpoint may even lack an HTML template). Consider passing return_to/return_label in as locals from the host page and either excluding the link from the cached fragment or keying the cache on those locals (or disabling caching for this partial).
There was a problem hiding this comment.
okay i have noted this suggestion and added
<%= render "cards/display/previews",
cards: @page.records,
draggable: true,
return_to: request.fullpath,
return_label: @board.name %>
to all the pages rendering this.
i changed _preview.html.erb back and added return_to and return_label as locals in _previews
app/helpers/boards_helper.rb
Outdated
| uri = URI.parse(params[:return_to]) | ||
| return unless uri.host.nil? # prevents open redirect | ||
|
|
||
| params[:return_to] |
There was a problem hiding this comment.
safe_return_to only checks uri.host.nil?. This still allows dangerous/incorrect values like protocol-relative URLs (//evil.com/path) and non-HTTP schemes (javascript:...), both of which can produce external navigation or script execution when used as an href. Tighten validation to allow only same-origin relative paths (e.g., require uri.scheme.nil? && uri.host.nil?, and that the raw value starts with a single / but not //; optionally normalize to uri.request_uri).
| uri = URI.parse(params[:return_to]) | |
| return unless uri.host.nil? # prevents open redirect | |
| params[:return_to] | |
| raw = params[:return_to].to_s | |
| # Only allow same-origin relative paths starting with a single "/" | |
| return unless raw.start_with?("/") && !raw.start_with?("//") | |
| uri = URI.parse(raw) | |
| return unless uri.scheme.nil? && uri.host.nil? # prevents open redirect and non-HTTP schemes | |
| uri.request_uri |
There was a problem hiding this comment.
reviewed the suggestion and added conditions for safe redirects and avoiding non-http schemes
app/helpers/boards_helper.rb
Outdated
| def link_back_to_board(board) | ||
| back_link_to board.name, board, "keydown.left@document->hotkey#click keydown.esc@document->hotkey#click click->turbo-navigation#backIfSamePath" | ||
| path = safe_return_to || board | ||
| label = params[:return_label].presence || board.name | ||
| back_link_to( | ||
| label, | ||
| path, | ||
| "keydown.left@document->hotkey#click keydown.esc@document->hotkey#click click->turbo-navigation#backIfSamePath" | ||
| ) |
There was a problem hiding this comment.
link_back_to_board now changes behavior based on user-controlled query params (return_to / return_label). Given the security-sensitive nature (open redirect prevention) and the multiple edge cases (invalid URI, protocol-relative URLs, missing/blank label), it would be good to add a helper test file (e.g., test/helpers/boards_helper_test.rb) covering accepted relative paths and rejected external/scheme/protocol-relative values, and asserting fallback to board/board.name when params are missing/invalid.
There was a problem hiding this comment.
added a safe_path to avoid any open redirect risks.
also added a boards_helper_test file covering all accepted relative paths and rejecting external values.
|
@jeremy can you please review the changes . thank you |
jeremy
left a comment
There was a problem hiding this comment.
Take a look at FilterScoped. Rather than doing a generic return_to/return_label, what we need here is to preserve user filtering params.
app/helpers/application_helper.rb
Outdated
| def page_title_for_return | ||
| @page_title.presence | ||
| end |
There was a problem hiding this comment.
| def page_title_for_return | |
| @page_title.presence | |
| end |
This is now unused huh?
app/helpers/boards_helper.rb
Outdated
|
|
||
| raw | ||
| rescue URI::InvalidURIError | ||
| nil |
There was a problem hiding this comment.
Match the repo's code style, please. See STYLE.md: indent under method visibility, avoid early-return exits in favor of conditional expressions.
There was a problem hiding this comment.
thanks for the suggestion jeremy. i will match the repo's code style moving forward.
test/helpers/boards_helper_test.rb
Outdated
| @board = Board.find( | ||
| ActiveRecord::FixtureSet.identify("writebook", :uuid) | ||
| ) |
There was a problem hiding this comment.
| @board = Board.find( | |
| ActiveRecord::FixtureSet.identify("writebook", :uuid) | |
| ) | |
| @board = Board.find(ActiveRecord::FixtureSet.identify("writebook", :uuid)) |
| cards: @page.records, | ||
| draggable: true, | ||
| return_to: request.fullpath, | ||
| return_label: @board.name %> |
There was a problem hiding this comment.
Let's omit the defaults from all these partial renders. Should only need to pass when they differ.
There was a problem hiding this comment.
okay jeremy i have omitted the defaults from all these partial renders except index.html.erb as default differs here.
| @@ -1,3 +1,4 @@ | |||
| <%# expects: card:, return_to:, return_label: %> | |||
There was a problem hiding this comment.
Should be optional, defaulting to the board.
There was a problem hiding this comment.
okay i have added an optional condition here
|
@jeremy i’ve addressed the issues and made the requested changes. Could you please review them when you have a moment? Thank you. |
|
@jayohms Could you please review the pr and changes addresses in it when you have a moment? Thank you. |
Currently, when opening a card from filtered views such as “Assigned to Me”, the back link always returns to the card’s board.
This change introduces support for 'return_to' and 'return_label'
so that the back link preserves navigation context.
This keeps navigation stateless and improves UX consistency.