Skip to content

[#74684] Extract BorderBoxListComponent#23074

Open
myabc wants to merge 5 commits into
devfrom
implementation/74684-extract-border-box-list-component
Open

[#74684] Extract BorderBoxListComponent#23074
myabc wants to merge 5 commits into
devfrom
implementation/74684-extract-border-box-list-component

Conversation

@myabc
Copy link
Copy Markdown
Contributor

@myabc myabc commented May 5, 2026

Ticket

https://community.openproject.org/wp/74684

What are you trying to accomplish?

Extracts a reusable BorderBox-backed list component out of WorkPackageCardListComponent (renamed in #23146), splitting the responsibility across two layers:

  • OpenProject::Common::BorderBoxListComponent — A domain-aware list that renders Primer::Beta::BorderBox directly. Provides header, with_item, with_work_package_item, with_empty_state, and footer slots. Owns container-derived DOM IDs. Supports manual item composition (callers control render order).

  • Backlogs::WorkPackageCardListComponent — An internal auto-collection wrapper. Accepts work_packages:, iterates the collection, handles empty-state and drag-and-drop. Moved to Backlogs since it's not yet ready for sharing with other teams.

Backlogs InboxComponent uses BorderBoxListComponent directly (manual composition for show-more interleaving). SprintComponent and BucketComponent use the Backlogs wrapper.

What approach did you choose and why?

BorderBoxListComponent renders Primer::Beta::BorderBox directly with polymorphic item slots — generic content rows and work-package rows in the same collection. WorkPackageItem is a base class nested under BorderBoxListComponent that Backlogs subclasses for drag-and-drop and story-specific behavior.

Also switches Backlogs templates from data: { test_selector: ... } to the test_selector: Primer system argument.

Merge checklist

  • Added/updated tests
  • Added/updated documentation
    • Lookbook previews
    • YARDDoc
  • Tested major browsers (Chrome, Firefox, Edge, ...)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes a rename/refactor of the work package “card box” UI into a WorkPackageCardListComponent, updates Backlogs to use the renamed component/item class, and introduces a reusable OpPrimer::BorderBoxListComponent wrapper to standardize BorderBox list rendering (header/rows/footer/empty rows).

Changes:

  • Rename OpenProject::Common::WorkPackageCardBoxComponent to OpenProject::Common::WorkPackageCardListComponent (including nested Header/Item) and update associated styles and I18n keys.
  • Add OpPrimer::BorderBoxListComponent (with content/empty/work-package row bridges) and wire the work package card list to render via this component.
  • Update Backlogs (bucket/sprint/inbox) rendering and specs to use the new list + item components and updated accessibility labels.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated no comments.

Show a summary per file
File Description
spec/components/open_project/common/work_package_card_list_component/item_spec.rb Updates spec namespace for the renamed Item component and related test doubles.
spec/components/open_project/common/work_package_card_list_component/header_spec.rb Updates spec namespace for the renamed Header component.
spec/components/open_project/common/work_package_card_list_component_spec.rb Updates spec namespace for the renamed list component and removes coverage for removed manual-slot behavior.
spec/components/op_primer/border_box_list_component_spec.rb Adds unit coverage for the new OpPrimer::BorderBoxListComponent behavior.
modules/backlogs/spec/support/pages/backlog.rb Updates I18n key for counter accessible label; minor Capybara matcher adjustment.
modules/backlogs/spec/components/backlogs/work_package_card_list_item_component_spec.rb Updates spec to the renamed Backlogs item component.
modules/backlogs/spec/components/backlogs/bucket_component_spec.rb Updates I18n key used for the counter aria-label.
modules/backlogs/app/components/backlogs/work_package_card_list_item_component.rb Renames Backlogs work package item component to inherit from the renamed common list item.
modules/backlogs/app/components/backlogs/sprint_component.html.erb Switches sprint rendering to WorkPackageCardListComponent and renamed Backlogs item component.
modules/backlogs/app/components/backlogs/inbox_component.html.erb Reworks inbox rendering to use OpPrimer::BorderBoxListComponent directly and updates CSS class name.
modules/backlogs/app/components/backlogs/bucket_component.html.erb Switches bucket rendering to WorkPackageCardListComponent and renamed Backlogs item component.
lookbook/previews/open_project/common/work_package_card_list_component_preview.rb Renames preview class and updates it to render the renamed list component; removes manual-item preview.
lookbook/previews/op_primer/border_box_list_component_preview.rb Adds Lookbook preview coverage for the new BorderBoxListComponent.
config/locales/en.yml Renames translation namespace from work_package_card_box_component to work_package_card_list_component.
app/components/open_project/common/work_package_card_list_component/item.rb Renames outer component constant for the nested Item and keeps item row bridging behavior.
app/components/open_project/common/work_package_card_list_component/header.sass Renames header CSS class from ...card-box... to ...card-list....
app/components/open_project/common/work_package_card_list_component/header.rb Renames outer component constant for the nested Header.
app/components/open_project/common/work_package_card_list_component/header.html.erb Updates header wrapper class to the renamed CSS class.
app/components/open_project/common/work_package_card_list_component.sass Renames base CSS class from ...card-box... to ...card-list....
app/components/open_project/common/work_package_card_list_component.rb Renames the component class and simplifies rendering/validation to “work_packages-driven” list rendering.
app/components/open_project/common/work_package_card_list_component.html.erb Adds a dedicated template that renders via OpPrimer::BorderBoxListComponent.
app/components/op_primer/border_box_list_component/work_package_item.rb Replaces incorrect/spec-like content with the actual WorkPackageItem bridge implementation.
app/components/op_primer/border_box_list_component/empty_item.rb Moves/implements EmptyItem bridge under OpPrimer::BorderBoxListComponent.
app/components/op_primer/border_box_list_component/content_item.rb Moves/implements ContentItem bridge under OpPrimer::BorderBoxListComponent.
app/components/op_primer/border_box_list_component.rb Introduces the new BorderBox list wrapper component API.
app/components/op_primer/border_box_list_component.html.erb Updates rendering to use rows and render header/footer via stored bridge objects.
app/components/_index.sass Updates Sass imports to the renamed work package card list component styles.
Comments suppressed due to low confidence (2)

app/components/open_project/common/work_package_card_list_component/item.rb:36

  • The class-level comment still refers to a “card box”, but the constant is now WorkPackageCardListComponent. Please update the wording (and any similar occurrences) to avoid confusion for future readers and to reflect the renamed component accurately.
    app/components/open_project/common/work_package_card_list_component.rb:140
  • The surrounding documentation for the empty_state slot still talks about “items” / “automatic item builds”, but the component now renders rows directly from work_packages (and validates based on work_packages.empty?). Please update the comment to match the current behavior so it stays accurate.

@myabc myabc changed the title [#74684] Rename WorkPackageCardListComponent [#74684] Rename WorkPackageCardListComponent, Extract BorderBoxListComponent May 6, 2026
@myabc myabc added needs review ruby Pull requests that update Ruby code and removed DO NOT MERGE labels May 8, 2026
@myabc myabc requested review from EinLama May 8, 2026 12:01
Copy link
Copy Markdown
Contributor

@EinLama EinLama left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change makes sense to me 👍

It would be good to wait for the next merge of the release branch and apply the removal of the mirrorContainer here, too.

Comment thread modules/backlogs/app/components/backlogs/inbox_component.html.erb Outdated
Comment thread app/components/open_project/common/work_package_card_list_component.rb Outdated
@myabc myabc force-pushed the implementation/74684-extract-border-box-list-component branch from 20fd06f to 51ebb2f Compare May 8, 2026 17:57
@myabc myabc changed the title [#74684] Rename WorkPackageCardListComponent, Extract BorderBoxListComponent [#74684] Rename WorkPackageCardListComponent, Extract BorderBoxListComponent May 8, 2026
@myabc myabc force-pushed the implementation/74684-extract-border-box-list-component branch 3 times, most recently from 3005f6e to 1a753d1 Compare May 8, 2026 21:10
@myabc myabc requested a review from Copilot May 8, 2026 21:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

config/locales/en.yml:4913

  • Renaming the I18n scope from open_project.common.work_package_card_box_component to ...work_package_card_list_component updates only en.yml. All Crowdin locale files still define work_package_card_box_component and have no work_package_card_list_component, so non-English locales will start showing missing translations (or fallback to English) for the header count/labels. Consider keeping the old key as an alias (temporarily) and/or updating the Crowdin locale keys as part of this change.
      work_package_card_list_component:
        header:
          label_actions: "Open menu"
          label_work_package_count:
            zero: "No work packages"
            one: "%{count} work package"
            other: "%{count} work packages"

@myabc myabc force-pushed the implementation/74684-extract-border-box-list-component branch from 1a753d1 to a280bbb Compare May 8, 2026 21:58
@myabc myabc changed the title [#74684] Rename WorkPackageCardListComponent, Extract BorderBoxListComponent [#74684] Extract BorderBoxListComponent May 8, 2026
@myabc myabc changed the base branch from dev to implementation/74779-rename-work-package-card-list-component May 8, 2026 22:07
@myabc myabc force-pushed the implementation/74684-extract-border-box-list-component branch from a280bbb to f25a281 Compare May 8, 2026 22:18
@myabc myabc requested a review from HDinger May 10, 2026 17:07
Comment thread app/components/op_primer/border_box_list_component/header.html.erb Outdated
Comment thread app/components/op_primer/border_box_list_component.html.erb Outdated
@myabc myabc force-pushed the implementation/74684-extract-border-box-list-component branch 2 times, most recently from e46b196 to 39b5174 Compare May 10, 2026 19:15
@myabc myabc requested a review from Copilot May 10, 2026 19:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 3 comments.

Comment thread app/components/open_project/common/border_box_list_component/header.html.erb Outdated
Comment thread app/components/op_primer/border_box_list_component/item.html.erb Outdated
Comment thread app/components/open_project/common/work_package_card_list_component.rb Outdated
Base automatically changed from implementation/74779-rename-work-package-card-list-component to dev May 11, 2026 08:15
@myabc myabc requested a review from Copilot May 11, 2026 16:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 27 changed files in this pull request and generated 1 comment.

Comment thread app/components/open_project/common/border_box_list_component.rb
Comment thread modules/backlogs/app/components/backlogs/work_package_card_list_component.rb Outdated
Comment thread modules/backlogs/app/components/backlogs/work_package_card_list_component.rb Outdated
Comment thread app/components/open_project/common/border_box_list_component/header.html.erb Outdated
Comment thread app/components/open_project/common/border_box_list_component/header.html.erb Outdated
@myabc myabc force-pushed the implementation/74684-extract-border-box-list-component branch from 78c15af to 55f3271 Compare May 14, 2026 12:52
@myabc myabc requested a review from Copilot May 14, 2026 13:01
@myabc myabc force-pushed the implementation/74684-extract-border-box-list-component branch from 55f3271 to 01097e0 Compare May 14, 2026 13:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 30 changed files in this pull request and generated 1 comment.

Comment thread modules/backlogs/app/components/backlogs/work_package_card_list_component.rb Outdated
@myabc myabc force-pushed the implementation/74684-extract-border-box-list-component branch from 01097e0 to 737bafc Compare May 14, 2026 13:10
Introduces a shared BorderBox-backed list component and moves the
Backlogs-specific work package card list into the Backlogs module.

Wires Backlogs callers to the new component API, keeps specialized
card rendering behind an item factory hook, and replaces old OpPrimer
work-package card list coverage with focused component specs.

https://community.openproject.org/wp/74684
@myabc myabc force-pushed the implementation/74684-extract-border-box-list-component branch from 737bafc to 8f2cdab Compare May 14, 2026 14:18
myabc added 4 commits May 14, 2026 16:26
Replaces `data: { test_selector: ... }` with the `test_selector:`
system argument in Backlogs component templates that instantiate
`BorderBoxListComponent`.
Adds a `count_label:` keyword to `Header` that defaults to
`I18n.t(:label_x_items, count:)` and always sets `aria-live:
polite` on the counter badge. Callers no longer need to wire
up aria attributes manually. Uses `merge_aria` so additional
caller-provided aria attributes are preserved.

https://community.openproject.org/wp/74684
Adds `DEFAULT_ACTION_SCHEME` (`:default`) to `Header` so callers
no longer need to pass `scheme:` for standard header buttons.
Updates Lookbook preview and Backlogs sprint component to use the
new default.

https://community.openproject.org/wp/72945
@icon = icon

@system_arguments = system_arguments
@system_arguments[:role] = "status"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should every empty state be rendered as role="status" with aria-live="polite" by default?

It might be more announcement-heavy than needed. Maybe this should be configurable, with the current behavior as an option?

merged = DEFAULT_COUNT_ARGUMENTS.merge(count_arguments).merge(count:)
merged[:aria] = merge_aria(
merged,
{ aria: { label: count_label, live: "polite" } }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the counter always get aria-live="polite" by default?

For static initial rendering this may be unnecessary, and in Backlogs we may render several counters at once. Would it be safer to make the live behavior opt-in, or only apply it where the counter is expected to update dynamically?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this new file imported into the compiled frontend styles. Could we import it?

@system_arguments = system_arguments

@system_arguments[:id] ||= dom_target(container)
@system_arguments[:list_id] = dom_target(@system_arguments[:id], :list)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@system_arguments[:list_id] = dom_target(...) unconditionally overwrites any caller-provided list_id:. The spec ("derives the list id from the explicit box id") even asserts that an explicit list_id: is ignored. Same for footer id: at line 168 — the Footer stores the original in attr_reader :id but the rendered id is always the derived one. If these aren't meant to be caller-controlled, consider filtering them out or documenting they're reserved, rather than silently discarding them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deny_single_argument would make this easy to implement, but we would have to inherit from Primer::Component instead.

include Primer::AttributesHelper
include HasMenu

DEFAULT_ACTION_SCHEME = :default
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old WorkPackageCardListComponent header never set a default button scheme, and sprint_component.rb explicitly passed scheme: :invisible on the start/finish sprint buttons. But here we remove those scheme: :invisible overrides, so the sprint buttons now render with :default scheme instead of :invisible. This is a user-visible UI change. Is it intentional? If so, worth calling out in the PR description.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's intentional. It's preparatory work for container header restyling in #23215 / OP #72945. I should have mentioned this small visual change in the PR description.

# @return [void]
def resolve_count!(item_count)
@count = item_count if count == true
@count_label ||= I18n.t(:label_x_items, count: @count) if render_count?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default label is intentionally generic ("5 items"). The Backlogs wrapper compensates by passing :label_x_work_packages. A short inline comment noting "callers should supply count_label: when a domain-specific label is needed" would help future consumers of this generic component avoid accidentally shipping "5 items" when they meant "5 work packages" (or similar).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bsatarnejad count_label is documented on Header#initialize. Is this not enough?

        # @param count_label [String, nil] accessible label for the counter
        #   badge. Defaults to `I18n.t(:label_x_items, count:)` when a count
        #   is rendered. Pass an explicit string to override.

<% end %>

<% if menu? %>
<% grid.with_area(:menu) do %>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old header template always rendered the menu grid area. This now conditionally renders it with if menu? This is cleaner, but worth confirming no CSS or JS relied on the .op-border-box-list-header--menu area always being present in the grid layout.

def call
blankslate = Primer::Beta::Blankslate.new(**@system_arguments)
blankslate.with_heading(tag: :h4).with_content(@title)
blankslate.with_description { @description } if @description
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with_description { @description } works but the old code used with_description_content(description), which is the Primer documented API for passing a plain string. The block form is fine but slightly inconsistent with the chaining style used for with_heading two lines above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DO NOT MERGE ruby Pull requests that update Ruby code

Development

Successfully merging this pull request may close these issues.

4 participants