Skip to content

chore: converted mixins to controllers and refactored badge#6130

Open
caseyisonit wants to merge 4 commits intomainfrom
caseyisonit/separated-core-cleanup
Open

chore: converted mixins to controllers and refactored badge#6130
caseyisonit wants to merge 4 commits intomainfrom
caseyisonit/separated-core-cleanup

Conversation

@caseyisonit
Copy link
Copy Markdown
Contributor

@caseyisonit caseyisonit commented Apr 2, 2026

Description

Converts the ObserveSlotPresence and ObserveSlotText mixins in the 2nd-gen core package into reactive controllers (SlotPresenceController and SlotTextController), and updates BadgeBase as the first consumer of the new pattern.

This follows the mixin composition guide recommendation to prefer controllers over deeply nested mixin chains.

What changed

New controllers (2nd-gen/packages/core/controllers/)

  • SlotPresenceController — observes whether slotted content matching CSS selectors is present in the host's light DOM. Replaces ObserveSlotPresence mixin.
  • SlotTextController — observes whether a slot contains meaningful text or element content. Replaces -ObserveSlotText mixin. Requires binding handleSlotChange to the slot's @slotchange event in the template.

Deleted mixins (2nd-gen/packages/core/mixins/)

  • observe-slot-presence.ts — removed; functionality moved to SlotPresenceController.
  • observe-slot-text.ts — removed; functionality moved to SlotTextController.
  • Removed corresponding exports from mixins/index.ts.

Updated components

  • Badge.base.ts — replaced triple-mixin inheritance (SizedMixin(ObserveSlotText(ObserveSlotPresence(...)))) with SizedMixin(SpectrumElement) plus two controller instances (slotPresence, slotText). Added hasIcon and slotHasContent getter wrappers.
  • Badge.ts (SWC) — added @slotchange=${this.slotText.handleSlotChange} to the default slot.

Documentation

  • Updated MIGRATION.md and README.md to reflect the new controller locations and remove mixin references.
    Motivation and context
  • BadgeBase previously used a 3-level mixin chain: SizedMixin(ObserveSlotText(ObserveSlotPresence(SpectrumElement))). This violated the project's mixin composition guideline (max 2 levels) and made the class hierarchy harder to reason about. Reactive controllers are the idiomatic Lit pattern for cross-cutting concerns — they compose without deepening the prototype chain, are easier to test in isolation, and can be added or removed without restructuring the class hierarchy.

Related issue(s)

fixes [Issue Number]

Screenshots (if appropriate)

N/A — no visual changes. The controllers reproduce the same behavior as the mixins they replace.

Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Testing for this change relies on CI passing. The controllers reproduce identical behavior to the mixins, so existing Badge tests validate correctness.

  • CI pipeline passes (build, lint, unit tests, VRTs)

  • Badge renders icon-only, text-only, and icon+text variants correctly

  • No remaining imports of ObserveSlotPresence or ObserveSlotText in 2nd-gen/

@caseyisonit caseyisonit requested a review from a team as a code owner April 2, 2026 00:33
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 2, 2026

⚠️ No Changeset found

Latest commit: 4328902

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@caseyisonit caseyisonit added Status:Ready for review PR ready for review or re-review. 2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. High priority PR review PR is a high priority and should be reviewed ASAP labels Apr 2, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

📚 Branch Preview Links

🔍 First Generation Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6130

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@Rajdeepc
Copy link
Copy Markdown
Contributor

Rajdeepc commented Apr 2, 2026

What is the win here? We don't have any gates to verify this new behaviour. This is risky.

Copy link
Copy Markdown
Contributor

@nikkimk nikkimk left a comment

Choose a reason for hiding this comment

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

I like the idea of this, but I would like a few more things before I do a more in-depth review:

  • simple stories that implement these controllers
  • documentation for these controllers in a controllers section of storyboook
  • tests specifically for the controllers

Base automatically changed from caseyisonit/separate-concerns to main April 3, 2026 15:24
@caseyisonit
Copy link
Copy Markdown
Contributor Author

caseyisonit commented Apr 3, 2026

What is the win here? We don't have any gates to verify this new behaviour. This is risky.

@Rajdeepc the win is that these should have always been controllers not mixins, this dramatically reduces the complexity of API inheritence and follows the code style guidelines on controllers. There is little to no risk to this at all at this point in our migration. Badge is still working as expected and i will implement @nikkimk feedback for docs and tests.

Copy link
Copy Markdown
Contributor

@Rajdeepc Rajdeepc left a comment

Choose a reason for hiding this comment

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

Clean implementation and code quality seems really nice but its not merge ready until there is some tests and changeset.

A few feedback:

  1. +1 to @nikkimk 's review request.
  2. We removed and added new public exports so we need changeset here.

Comment on lines +106 to +108
if (changed) {
this.host.requestUpdate();
}
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 pattern was that we deferred the re-render through this.updateComplete.then(() => this.requestUpdate()): but now this is synchronous. In practice this may be fine because Lit batches requestUpdate() calls, but it's a behavioural change that should be explicitly noted and tested.

Comment on lines +162 to 164
protected get slotHasContent(): boolean {
return this.slotText.hasContent;
}
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.

This is dead code. Please get clarification if 2nd-gen Badge component should support icon-only semantics or not or else this is a missing feature. Let's clarify this first.

}
return node.textContent ? node.textContent.trim().length > 0 : false;
});
this._hasContent = relevant.length > 0;
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.

Since this.slothasContent was a reactive property in update() it inherently was triggering Lit update cycle but now if initial content is present when hostConnected fires, the host won't get a re-render to reflect that state unless a subsequent render is already scheduled. This is likely fine during initial connection (a render is pending), but please verify with tests

@caseyisonit caseyisonit added Status:Addressing feedback PR owner is addressing review comments and will change label back to "Ready for review" when ready. and removed Status:Ready for review PR ready for review or re-review. labels Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. High priority PR review PR is a high priority and should be reviewed ASAP Status:Addressing feedback PR owner is addressing review comments and will change label back to "Ready for review" when ready.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants