[74625] Support async loading in FilterableTreeView - #451
Conversation
🦋 Changeset detectedLatest commit: bf466f0 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
474604c to
c8b856f
Compare
b2dfdfb to
3a1d770
Compare
There was a problem hiding this comment.
Pull request overview
This PR extends Primer::OpenProject::FilterableTreeView with an optional src: attribute to enable async (server-side) filtering, adds support for select_variant: :none (for link/navigation trees), and introduces a new trailing action slot for Primer::Alpha::TreeView nodes. It also adds previews, a demo controller/endpoint, and test coverage for the new async behaviors.
Changes:
- Add async filtering mode to
FilterableTreeViewviasrc:with debounced fetching, tree replacement, selection/expansion persistence, retained hidden inputs, and a loading skeleton. - Add
select_variant: :nonesupport to allow rendering navigational/link nodes. - Add
TreeViewtrailing action support (TrailingAction, slot plumbing, CSS, and previews).
Reviewed changes
Copilot reviewed 41 out of 80 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| test/system/open_project/filterable_tree_view_test.rb | System tests covering async loading, filtering, expansion/selection persistence, and form submission behaviors. |
| test/components/primer/open_project/filterable_tree_view_test.rb | Component tests for src:, select variants (including :none), and form inclusion/exclusion behavior. |
| app/components/primer/open_project/filterable_tree_view.rb | Adds src: API, supported select variants update, and guards against tree_view_arguments in async mode. |
| app/components/primer/open_project/filterable_tree_view.ts | Implements async fetch/replace flow, debouncing, selection/expansion snapshots, and retained hidden inputs. |
| app/components/primer/open_project/filterable_tree_view.html.erb | Updates layout, adds scroll container + loading skeleton, and adjusts form-exclusion behavior for inputs. |
| app/components/primer/open_project/filterable_tree_view.pcss | Adds component styling (layout, loading skeleton visibility, highlight styles). |
| app/components/primer/primer.pcss | Imports the new filterable_tree_view.pcss stylesheet. |
| app/components/primer/open_project/filterable_tree_view/sub_tree.rb | Expands supported select variants and updates async-subtree error messaging. |
| app/components/primer/alpha/tree_view/trailing_action.rb | New component to render a trailing action wrapper for TreeView nodes. |
| app/components/primer/alpha/tree_view/trailing_action.html.erb | Template for trailing action container. |
| app/components/primer/alpha/tree_view/node.rb | Adds a generic trailing_action slot and data attribute for CSS layout. |
| app/components/primer/alpha/tree_view/node.html.erb | Renders the trailing action when present. |
| app/components/primer/alpha/tree_view/leaf_node.rb | Adds with_trailing_action_button helper for leaf nodes. |
| app/components/primer/alpha/tree_view/leaf_node.html.erb | Wires trailing action into the node composition. |
| app/components/primer/alpha/tree_view/sub_tree_node.rb | Adds with_trailing_action_button helper for sub-tree nodes. |
| app/components/primer/alpha/tree_view/sub_tree_node.html.erb | Wires trailing action into the node composition. |
| app/components/primer/alpha/tree_view.pcss | Updates TreeView grid layout to accommodate trailing actions and adjusts alignment. |
| previews/primer/open_project/filterable_tree_view_preview.rb | Adds Lookbook previews for async mode, async form input, and link nodes; updates select_variant param options. |
| previews/primer/open_project/filterable_tree_view_preview/async.html.erb | New async preview template using src:. |
| previews/primer/open_project/filterable_tree_view_preview/async_form_input.html.erb | New async + form preview template for retained selections and include_sub_items submission. |
| previews/primer/open_project/filterable_tree_view_preview/link_nodes.html.erb | New preview demonstrating select_variant: :none link-style nodes. |
| previews/primer/alpha/tree_view_preview.rb | Adds trailing_action_icon param to the TreeView leaf node playground. |
| previews/primer/alpha/tree_view_preview/leaf_node_playground.html.erb | Renders a trailing action button when configured in the playground. |
| config/routes.rb | Adds routes/endpoints for the async filterable tree demo responses. |
| app/controllers/primer/view_components/filterable_tree_view_items_controller.rb | Demo controller providing server-rendered <tree-view> fragments for async mode previews/tests. |
| app/views/primer/view_components/filterable_tree_view_items/index.html.erb | Server-rendered <tree-view> fragment for async filtering results. |
| app/views/primer/view_components/filterable_tree_view_items/async_form_tree.html.erb | Server-rendered <tree-view> fragment variant configured for form submissions. |
| app/views/primer/view_components/filterable_tree_view_items/_node.html.erb | Partial to render demo nodes with stable data-node-id and expanded hierarchy during filtering. |
| .playwright/screenshots/snapshots.test.ts-snapshots/primer/open_project/filterable_tree_view/default/aria-snapshot.yml | Updated ARIA snapshot due to toolbar layout changes. |
| .playwright/screenshots/snapshots.test.ts-snapshots/primer/open_project/filterable_tree_view/default/aria-snapshot--after-interaction.yml | Updated ARIA snapshot after interaction. |
| .playwright/screenshots/snapshots.test.ts-snapshots/primer/open_project/filterable_tree_view/custom_segmented_control/aria-snapshot.yml | Updated ARIA snapshot for custom segmented control preview. |
| .playwright/screenshots/snapshots.test.ts-snapshots/primer/open_project/filterable_tree_view/custom_segmented_control/aria-snapshot--after-interaction.yml | Updated ARIA snapshot after interaction. |
| static/previews.json | Regenerated static preview metadata for new previews. |
| static/info_arch.json | Regenerated info-architecture metadata for new slots/components and updated docs. |
| static/arguments.json | Regenerated argument metadata including src: and trailing action component. |
| static/constants.json | Regenerated constants mapping including new TrailingAction and supported select variants. |
| static/classes.json | Regenerated CSS class mapping for new FilterableTreeView classes. |
| static/statuses.json | Regenerated status map including new alpha component. |
| static/audited_at.json | Regenerated audited-at map including new alpha component. |
| .changeset/loud-paths-taste.md | Changeset for trailing action addition. |
| .changeset/afraid-poets-sing.md | Changeset for async loading strategy. |
myabc
left a comment
There was a problem hiding this comment.
With the exception of the Preview controllers, the Ruby code looks good. 👍🏻
Functionally, this seems acceptable as an interim solution, though I'm still not fully comfortable with the amount of manual, imperative state synchronisation between the frontend and backend. From a UX perspective, the flickering during filtering makes the experience feel a little less polished than we'd ideally want ^*.
Screen.Recording.2026-05-12.at.21.10.51.mov
We already have at least two mechanisms for loading HTML from the backend: Primer's include-fragment and Turbo frames. I would prefer not to introduce another. If we make raw fetch requests, we need to a) allow custom headers to be passed in (CSRF-Token, credentials, etc.) and b) make sure callers don't forget.
I still need to review the downstream PR.
^* I created a small experiment (#452) to see if Idiomorph might help but didn't get very far. The flicker is still present - it's not simply a result of recreating DOM nodes.
7bb0691 to
8fc6557
Compare
Yeah, I am also not 100% happy about this but I did not find good alternative given that the downstream endpoints should not care about all of this..
We could increase the debounce a bit, but I guess there will always be some kind of visual flickering. This is the case in the current (angular) project selector as well.
I tried with include-fragment at first (see #449). However, I did not really manage with that approach which is why I switched. |
* Add tests for TrailingActionButton * Fix eslint issues * Include review feedback
608b7af to
cf4723e
Compare
|
|
||
| try { | ||
| const response = await fetch(url.toString(), {signal, headers: {Accept: 'text/html'}}) | ||
| if (!response.ok) return |
There was a problem hiding this comment.
Do we need more error handling than this?
myabc
left a comment
There was a problem hiding this comment.
This looks good. However it would be good to check with @oliverguenther about fetch headers (and also potentially CSP - which is something include-fragment-element attempts to handle)
What are you trying to accomplish?
Extends
FilterableTreeViewwith an optionalsrc: attributethat activates server-side filtering mode. When set, filter interactions no longer happen client-side but the component sends debounced requests to the server and replaces the current tree with the filtered response.Further, the component now also supports
select_variant: :noneto allow rendering links for navigational contextsList the issues that this change affects.
https://community.openproject.org/wp/74625
Risk Assessment
What approach did you choose and why?
Behavior
Filtering
query,filter_mode, andinclude_sub_itemsas query parametersExpansion state
Selection persistence
data-node-iddata-filterable-tree-view-retained), ensuring a complete payload regardless of what is currently visibleInclude sub-items
Usage
The server endpoint must return a HTML fragment. Each node should carry a stable data-node-id on its [role=treeitem] element.
Merge checklist