From 273a2437e1c05540762a0d41ecec6c2fb6c4c82a Mon Sep 17 00:00:00 2001
From: Lee Rowlands <lee.rowlands@previousnext.com.au>
Date: Tue, 14 Jan 2025 10:21:41 +1000
Subject: [PATCH 01/30] Issue #3498419: Support PHP defined component lists

---
 dictionary.txt |   4 +
 openapi.yml    | 520 ++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 455 insertions(+), 69 deletions(-)

diff --git a/dictionary.txt b/dictionary.txt
index c1402f512b..579baf9f7f 100644
--- a/dictionary.txt
+++ b/dictionary.txt
@@ -59,3 +59,7 @@ cebe
 inwidget
 uuidv
 Linkit
+Modulz
+Pilcrow
+Vercel
+
diff --git a/openapi.yml b/openapi.yml
index 2aa4d5d3b1..74903124f6 100644
--- a/openapi.yml
+++ b/openapi.yml
@@ -159,76 +159,451 @@ paths:
               examples:
                 singleAvailableComponent:
                   value:
-                    'sdc_test:my-cta':
-                      id: 'sdc_test:my-cta'
-                      name: Call to Action
-                      source: Module component
-                      category: Buttons
-                      transforms:
-                        text:
-                          extractMainPropertyName:
-                            mainPropertyName: value
-                        href:
-                          extractMainPropertyName:
-                            mainPropertyName: value
-                        target:
-                          passThrough: {  }
-                      metadata:
-                        path: >-
-                          core/modules/system/tests/modules/sdc_test/components/my-cta
-                        documentation: >-
-                          No documentation found. Add a README.md in your
-                          component directory.
-                        status: stable
-                        machineName: my-cta
+                    lists:
+                      components:
+                        label: Components
+                        position: library
+                        icon: Component1
+                        weight: 1
+                      global_components:
+                        label: Dynamic Components
+                        position: header
+                        icon: File
+                        weight: 2
+                    components:
+                      'sdc_test:my-cta':
+                        id: 'sdc_test:my-cta'
                         name: Call to Action
-                        group: All Components
+                        source: Module component
+                        category: Buttons
+                        transforms:
+                          text:
+                            extractMainPropertyName:
+                              mainPropertyName: value
+                          href:
+                            extractMainPropertyName:
+                              mainPropertyName: value
+                          target:
+                            passThrough: {  }
+                        metadata:
+                          path: >-
+                            core/modules/system/tests/modules/sdc_test/components/my-cta
+                          documentation: >-
+                            No documentation found. Add a README.md in your
+                            component directory.
+                          status: stable
+                          machineName: my-cta
+                          name: Call to Action
+                          group: All Components
+                          schema:
+                            type: object
+                            required:
+                              - text
+                            properties:
+                              text:
+                                type:
+                                  - string
+                                  - object
+                                title: Title
+                                description: The title for the cta
+                                examples:
+                                  - Press
+                                  - Submit now
+                              href:
+                                type:
+                                  - string
+                                  - object
+                                title: URL
+                                format: uri
+                              target:
+                                type:
+                                  - string
+                                  - object
+                                title: Target
+                                enum:
+                                  - ''
+                                  - _blank
+                              attributes:
+                                type:
+                                  - Drupal\Core\Template\Attribute
+                                  - object
+                                name: Attributes
+                            additionalProperties: false
+                        description: Call to action link.
+                        mandatorySchemas: true
+                        slots: []
+              schema:
+                type: object
+                required:
+                  - components
+                  - lists
+                properties:
+                  components:
+                    title: 'Available components'
+                    type: object
+                    minProperties: 1
+                    # @todo Validate the property keys match the
+                    #   '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in
+                    #    https://drupal.org/i/3506543.
+                    # @see \Drupal\Core\Plugin\Component::$machineName
+                    additionalProperties:
+                      $ref: '#/components/schemas/Component'
+                  lists:
+                    type: object
+                    minProperties: 1
+                    patternProperties:
+                      '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$':
                         schema:
                           type: object
                           required:
-                            - text
+                            - label
+                            - position
+                            - icon
+                            - weight
                           properties:
-                            text:
-                              type:
-                                - string
-                                - object
-                              title: Title
-                              description: The title for the cta
-                              examples:
-                                - Press
-                                - Submit now
-                            href:
-                              type:
-                                - string
-                                - object
-                              title: URL
-                              format: uri
-                            target:
-                              type:
-                                - string
-                                - object
-                              title: Target
+                            label:
+                              type: string
+                              title: Component list label
+                              example: Components
+                            position:
+                              type: string
+                              title: Where the component list should be shown
+                              example: library
                               enum:
-                                - ''
-                                - _blank
-                            attributes:
-                              type:
-                                - Drupal\Core\Template\Attribute
-                                - object
-                              name: Attributes
-                          additionalProperties: false
-                      description: Call to action link.
-                      mandatorySchemas: true
-                      slots: []
-              schema:
-                type: object
-                minProperties: 1
-                # @todo Validate the property keys match the
-                #   '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in
-                #    https://drupal.org/i/3506543.
-                # @see \Drupal\Core\Plugin\Component::$machineName
-                additionalProperties:
-                  $ref: '#/components/schemas/Component'
+                                # This may be extended as the UI evolves.
+                                - library
+                                - header
+                            weight:
+                              type: integer
+                              title: List weight
+                              example: -1
+                              description: >
+                                Weight of lists relative to other lists in the same position.
+                                Larger numbers appear later in the order.
+                            icon:
+                              type: string
+                              title: Icon to use for list
+                              example: Component1
+                              enum:
+                                # Taken from https://www.radix-ui.com/icons
+                                - Accessibility
+                                - ActivityLog
+                                - AlignBaseline
+                                - AlignBottom
+                                - AlignCenterHorizontally
+                                - AlignCenterVertically
+                                - AlignLeft
+                                - AlignRight
+                                - AlignTop
+                                - AllSides
+                                - Angle
+                                - Archive
+                                - ArrowBottomLeft
+                                - ArrowBottomRight
+                                - ArrowDown
+                                - ArrowLeft
+                                - ArrowRight
+                                - ArrowTopLeft
+                                - ArrowTopRight
+                                - ArrowUp
+                                - AspectRatio
+                                - Avatar
+                                - Backpack
+                                - Badge
+                                - BarChart
+                                - Bell
+                                - BlendingMode
+                                - Bookmark
+                                - BookmarkFilled
+                                - BorderAll
+                                - BorderBottom
+                                - BorderDashed
+                                - BorderDotted
+                                - BorderLeft
+                                - BorderNone
+                                - BorderRight
+                                - BorderSolid
+                                - BorderSplit
+                                - BorderStyle
+                                - BorderTop
+                                - BorderWidth
+                                - Box
+                                - BoxModel
+                                - Button
+                                - Calendar
+                                - Camera
+                                - CardStack
+                                - CardStackMinus
+                                - CardStackPlus
+                                - CaretDown
+                                - CaretLeft
+                                - CaretRight
+                                - CaretSort
+                                - CaretUp
+                                - ChatBubble
+                                - Check
+                                - CheckCircled
+                                - Checkbox
+                                - ChevronDown
+                                - ChevronLeft
+                                - ChevronRight
+                                - ChevronUp
+                                - Circle
+                                - CircleBackslash
+                                - Clipboard
+                                - ClipboardCopy
+                                - Clock
+                                - Code
+                                - CodeSandboxLogo
+                                - ColorWheel
+                                - ColumnSpacing
+                                - Columns
+                                - Commit
+                                - Component1
+                                - Component2
+                                - ComponentBoolean
+                                - ComponentInstance
+                                - ComponentNone
+                                - ComponentPlaceholder
+                                - Container
+                                - Cookie
+                                - Copy
+                                - CornerBottomLeft
+                                - CornerBottomRight
+                                - CornerTopLeft
+                                - CornerTopRight
+                                - Corners
+                                - CountdownTimer
+                                - CounterClockwiseClock
+                                - Crop
+                                - Cross1
+                                - Cross2
+                                - CrossCircled
+                                - Crosshair1
+                                - Crosshair2
+                                - CrumpledPaper
+                                - Cube
+                                - CursorArrow
+                                - CursorText
+                                - Dash
+                                - Dashboard
+                                - Desktop
+                                - Dimensions
+                                - Disc
+                                - DiscordLogo
+                                - DividerHorizontal
+                                - DividerVertical
+                                - Dot
+                                - DotFilled
+                                - DotsHorizontal
+                                - DotsVertical
+                                - DoubleArrowDown
+                                - DoubleArrowLeft
+                                - DoubleArrowRight
+                                - DoubleArrowUp
+                                - Download
+                                - DragHandleDots1
+                                - DragHandleDots2
+                                - DragHandleHorizontal
+                                - DragHandleVertical
+                                - DrawingPin
+                                - DrawingPinFilled
+                                - DropdownMenu
+                                - Enter
+                                - EnterFullScreen
+                                - EnvelopeClosed
+                                - EnvelopeOpen
+                                - Eraser
+                                - ExclamationTriangle
+                                - Exit
+                                - ExitFullScreen
+                                - ExternalLink
+                                - EyeClosed
+                                - EyeNone
+                                - EyeOpen
+                                - Face
+                                - FigmaLogo
+                                - File
+                                - FileMinus
+                                - FilePlus
+                                - FileText
+                                - FontBold
+                                - FontFamily
+                                - FontItalic
+                                - FontRoman
+                                - FontSize
+                                - FontStyle
+                                - Frame
+                                - FramerLogo
+                                - Gear
+                                - GitHubLogo
+                                - Globe
+                                - Grid
+                                - Group
+                                - Half1
+                                - Half2
+                                - HamburgerMenu
+                                - Hand
+                                - Heading
+                                - Heart
+                                - HeartFilled
+                                - Height
+                                - HobbyKnife
+                                - Home
+                                - IconJarLogo
+                                - IdCard
+                                - Image
+                                - InfoCircled
+                                - Input
+                                - InstagramLogo
+                                - Keyboard
+                                - LapTimer
+                                - Laptop
+                                - Layers
+                                - Layout
+                                - LetterCaseCapitalize
+                                - LetterCaseLowercase
+                                - LetterCaseToggle
+                                - LetterCaseUppercase
+                                - LetterSpacing
+                                - LightningBolt
+                                - LineHeight
+                                - Link1
+                                - Link2
+                                - LinkBreak1
+                                - LinkBreak2
+                                - LinkNone1
+                                - LinkNone2
+                                - LinkedInLogo
+                                - ListBullet
+                                - LockClosed
+                                - LockOpen1
+                                - LockOpen2
+                                - Loop
+                                - MagicWand
+                                - MagnifyingGlass
+                                - Margin
+                                - MaskOff
+                                - MaskOn
+                                - Minus
+                                - MinusCircled
+                                - Mix
+                                - MixerHorizontal
+                                - MixerVertical
+                                - Mobile
+                                - ModulzLogo
+                                - Moon
+                                - Move
+                                - NotionLogo
+                                - Opacity
+                                - OpenInNewWindow
+                                - Overline
+                                - Padding
+                                - PaperPlane
+                                - Pause
+                                - Pencil1
+                                - Pencil2
+                                - Person
+                                - PieChart
+                                - Pilcrow
+                                - PinBottom
+                                - PinLeft
+                                - PinRight
+                                - PinTop
+                                - Play
+                                - Plus
+                                - PlusCircled
+                                - QuestionMark
+                                - QuestionMarkCircled
+                                - Quote
+                                - Radiobutton
+                                - Reader
+                                - Reload
+                                - Reset
+                                - Resume
+                                - Rocket
+                                - RotateCounterClockwise
+                                - RowSpacing
+                                - Rows
+                                - RulerHorizontal
+                                - RulerSquare
+                                - Scissors
+                                - Section
+                                - SewingPin
+                                - SewingPinFilled
+                                - Shadow
+                                - ShadowInner
+                                - ShadowNone
+                                - ShadowOuter
+                                - Share1
+                                - Share2
+                                - Shuffle
+                                - Size
+                                - SketchLogo
+                                - Slash
+                                - Slider
+                                - SpaceBetweenHorizontally
+                                - SpaceBetweenVertically
+                                - SpaceEvenlyHorizontally
+                                - SpaceEvenlyVertically
+                                - SpeakerLoud
+                                - SpeakerModerate
+                                - SpeakerOff
+                                - SpeakerQuiet
+                                - Square
+                                - Stack
+                                - Star
+                                - StarFilled
+                                - StitchesLogo
+                                - Stop
+                                - Stopwatch
+                                - StretchHorizontally
+                                - StretchVertically
+                                - Strikethrough
+                                - Sun
+                                - Switch
+                                - Symbol
+                                - Table
+                                - Target
+                                - Text
+                                - TextAlignBottom
+                                - TextAlignCenter
+                                - TextAlignJustify
+                                - TextAlignLeft
+                                - TextAlignMiddle
+                                - TextAlignRight
+                                - TextAlignTop
+                                - TextNone
+                                - ThickArrowDown
+                                - ThickArrowLeft
+                                - ThickArrowRight
+                                - ThickArrowUp
+                                - Timer
+                                - Tokens
+                                - TrackNext
+                                - TrackPrevious
+                                - Transform
+                                - TransparencyGrid
+                                - Trash
+                                - TriangleDown
+                                - TriangleLeft
+                                - TriangleRight
+                                - TriangleUp
+                                - TwitterLogo
+                                - Underline
+                                - Update
+                                - Upload
+                                - Value
+                                - ValueNone
+                                - VercelLogo
+                                - Video
+                                - ViewGrid
+                                - ViewHorizontal
+                                - ViewNone
+                                - ViewVertical
+                                - Width
+                                - ZoomIn
+                                - ZoomOut
   /xb/api/config/js_component:
     get:
       description: Collection of Code Components
@@ -1089,6 +1464,7 @@ components:
         - id
         - category
         - default_markup
+        - list
       properties:
         name:
           type: string
@@ -1136,6 +1512,12 @@ components:
         dynamic_prop_source_candidates:
           type: object
           description: the DynamicPropSources that match each
+        list:
+          type: string
+          label: Component list
+          description: >
+            The list in which this component should appear. A list is used to group components in the UI.
+        additionalProperties: false
     CodeComponent:
       title: Code Component
       type: object
@@ -1287,11 +1669,11 @@ components:
             - uri
             - id
           examples:
-            - Logged in user:
-                name: 'Garrett Bobby Ferguson, Jr'
-                avatar: /sites/default/files/user-pictures/garrett.jpg
-                uri: /user/12
-                id: 12
+            Logged in user:
+              name: 'Garrett Bobby Ferguson, Jr'
+              avatar: /sites/default/files/user-pictures/garrett.jpg
+              uri: /user/12
+              id: 12
           properties:
             name:
               type: string
-- 
GitLab


From b2e8f046f5779bbd35a38809758f53878cb70c0e Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 13:49:08 +0000
Subject: [PATCH 02/30] Revert openapi changes

---
 openapi.yml | 520 +++++++---------------------------------------------
 1 file changed, 69 insertions(+), 451 deletions(-)

diff --git a/openapi.yml b/openapi.yml
index 74903124f6..2aa4d5d3b1 100644
--- a/openapi.yml
+++ b/openapi.yml
@@ -159,451 +159,76 @@ paths:
               examples:
                 singleAvailableComponent:
                   value:
-                    lists:
-                      components:
-                        label: Components
-                        position: library
-                        icon: Component1
-                        weight: 1
-                      global_components:
-                        label: Dynamic Components
-                        position: header
-                        icon: File
-                        weight: 2
-                    components:
-                      'sdc_test:my-cta':
-                        id: 'sdc_test:my-cta'
+                    'sdc_test:my-cta':
+                      id: 'sdc_test:my-cta'
+                      name: Call to Action
+                      source: Module component
+                      category: Buttons
+                      transforms:
+                        text:
+                          extractMainPropertyName:
+                            mainPropertyName: value
+                        href:
+                          extractMainPropertyName:
+                            mainPropertyName: value
+                        target:
+                          passThrough: {  }
+                      metadata:
+                        path: >-
+                          core/modules/system/tests/modules/sdc_test/components/my-cta
+                        documentation: >-
+                          No documentation found. Add a README.md in your
+                          component directory.
+                        status: stable
+                        machineName: my-cta
                         name: Call to Action
-                        source: Module component
-                        category: Buttons
-                        transforms:
-                          text:
-                            extractMainPropertyName:
-                              mainPropertyName: value
-                          href:
-                            extractMainPropertyName:
-                              mainPropertyName: value
-                          target:
-                            passThrough: {  }
-                        metadata:
-                          path: >-
-                            core/modules/system/tests/modules/sdc_test/components/my-cta
-                          documentation: >-
-                            No documentation found. Add a README.md in your
-                            component directory.
-                          status: stable
-                          machineName: my-cta
-                          name: Call to Action
-                          group: All Components
-                          schema:
-                            type: object
-                            required:
-                              - text
-                            properties:
-                              text:
-                                type:
-                                  - string
-                                  - object
-                                title: Title
-                                description: The title for the cta
-                                examples:
-                                  - Press
-                                  - Submit now
-                              href:
-                                type:
-                                  - string
-                                  - object
-                                title: URL
-                                format: uri
-                              target:
-                                type:
-                                  - string
-                                  - object
-                                title: Target
-                                enum:
-                                  - ''
-                                  - _blank
-                              attributes:
-                                type:
-                                  - Drupal\Core\Template\Attribute
-                                  - object
-                                name: Attributes
-                            additionalProperties: false
-                        description: Call to action link.
-                        mandatorySchemas: true
-                        slots: []
-              schema:
-                type: object
-                required:
-                  - components
-                  - lists
-                properties:
-                  components:
-                    title: 'Available components'
-                    type: object
-                    minProperties: 1
-                    # @todo Validate the property keys match the
-                    #   '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in
-                    #    https://drupal.org/i/3506543.
-                    # @see \Drupal\Core\Plugin\Component::$machineName
-                    additionalProperties:
-                      $ref: '#/components/schemas/Component'
-                  lists:
-                    type: object
-                    minProperties: 1
-                    patternProperties:
-                      '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$':
+                        group: All Components
                         schema:
                           type: object
                           required:
-                            - label
-                            - position
-                            - icon
-                            - weight
+                            - text
                           properties:
-                            label:
-                              type: string
-                              title: Component list label
-                              example: Components
-                            position:
-                              type: string
-                              title: Where the component list should be shown
-                              example: library
-                              enum:
-                                # This may be extended as the UI evolves.
-                                - library
-                                - header
-                            weight:
-                              type: integer
-                              title: List weight
-                              example: -1
-                              description: >
-                                Weight of lists relative to other lists in the same position.
-                                Larger numbers appear later in the order.
-                            icon:
-                              type: string
-                              title: Icon to use for list
-                              example: Component1
+                            text:
+                              type:
+                                - string
+                                - object
+                              title: Title
+                              description: The title for the cta
+                              examples:
+                                - Press
+                                - Submit now
+                            href:
+                              type:
+                                - string
+                                - object
+                              title: URL
+                              format: uri
+                            target:
+                              type:
+                                - string
+                                - object
+                              title: Target
                               enum:
-                                # Taken from https://www.radix-ui.com/icons
-                                - Accessibility
-                                - ActivityLog
-                                - AlignBaseline
-                                - AlignBottom
-                                - AlignCenterHorizontally
-                                - AlignCenterVertically
-                                - AlignLeft
-                                - AlignRight
-                                - AlignTop
-                                - AllSides
-                                - Angle
-                                - Archive
-                                - ArrowBottomLeft
-                                - ArrowBottomRight
-                                - ArrowDown
-                                - ArrowLeft
-                                - ArrowRight
-                                - ArrowTopLeft
-                                - ArrowTopRight
-                                - ArrowUp
-                                - AspectRatio
-                                - Avatar
-                                - Backpack
-                                - Badge
-                                - BarChart
-                                - Bell
-                                - BlendingMode
-                                - Bookmark
-                                - BookmarkFilled
-                                - BorderAll
-                                - BorderBottom
-                                - BorderDashed
-                                - BorderDotted
-                                - BorderLeft
-                                - BorderNone
-                                - BorderRight
-                                - BorderSolid
-                                - BorderSplit
-                                - BorderStyle
-                                - BorderTop
-                                - BorderWidth
-                                - Box
-                                - BoxModel
-                                - Button
-                                - Calendar
-                                - Camera
-                                - CardStack
-                                - CardStackMinus
-                                - CardStackPlus
-                                - CaretDown
-                                - CaretLeft
-                                - CaretRight
-                                - CaretSort
-                                - CaretUp
-                                - ChatBubble
-                                - Check
-                                - CheckCircled
-                                - Checkbox
-                                - ChevronDown
-                                - ChevronLeft
-                                - ChevronRight
-                                - ChevronUp
-                                - Circle
-                                - CircleBackslash
-                                - Clipboard
-                                - ClipboardCopy
-                                - Clock
-                                - Code
-                                - CodeSandboxLogo
-                                - ColorWheel
-                                - ColumnSpacing
-                                - Columns
-                                - Commit
-                                - Component1
-                                - Component2
-                                - ComponentBoolean
-                                - ComponentInstance
-                                - ComponentNone
-                                - ComponentPlaceholder
-                                - Container
-                                - Cookie
-                                - Copy
-                                - CornerBottomLeft
-                                - CornerBottomRight
-                                - CornerTopLeft
-                                - CornerTopRight
-                                - Corners
-                                - CountdownTimer
-                                - CounterClockwiseClock
-                                - Crop
-                                - Cross1
-                                - Cross2
-                                - CrossCircled
-                                - Crosshair1
-                                - Crosshair2
-                                - CrumpledPaper
-                                - Cube
-                                - CursorArrow
-                                - CursorText
-                                - Dash
-                                - Dashboard
-                                - Desktop
-                                - Dimensions
-                                - Disc
-                                - DiscordLogo
-                                - DividerHorizontal
-                                - DividerVertical
-                                - Dot
-                                - DotFilled
-                                - DotsHorizontal
-                                - DotsVertical
-                                - DoubleArrowDown
-                                - DoubleArrowLeft
-                                - DoubleArrowRight
-                                - DoubleArrowUp
-                                - Download
-                                - DragHandleDots1
-                                - DragHandleDots2
-                                - DragHandleHorizontal
-                                - DragHandleVertical
-                                - DrawingPin
-                                - DrawingPinFilled
-                                - DropdownMenu
-                                - Enter
-                                - EnterFullScreen
-                                - EnvelopeClosed
-                                - EnvelopeOpen
-                                - Eraser
-                                - ExclamationTriangle
-                                - Exit
-                                - ExitFullScreen
-                                - ExternalLink
-                                - EyeClosed
-                                - EyeNone
-                                - EyeOpen
-                                - Face
-                                - FigmaLogo
-                                - File
-                                - FileMinus
-                                - FilePlus
-                                - FileText
-                                - FontBold
-                                - FontFamily
-                                - FontItalic
-                                - FontRoman
-                                - FontSize
-                                - FontStyle
-                                - Frame
-                                - FramerLogo
-                                - Gear
-                                - GitHubLogo
-                                - Globe
-                                - Grid
-                                - Group
-                                - Half1
-                                - Half2
-                                - HamburgerMenu
-                                - Hand
-                                - Heading
-                                - Heart
-                                - HeartFilled
-                                - Height
-                                - HobbyKnife
-                                - Home
-                                - IconJarLogo
-                                - IdCard
-                                - Image
-                                - InfoCircled
-                                - Input
-                                - InstagramLogo
-                                - Keyboard
-                                - LapTimer
-                                - Laptop
-                                - Layers
-                                - Layout
-                                - LetterCaseCapitalize
-                                - LetterCaseLowercase
-                                - LetterCaseToggle
-                                - LetterCaseUppercase
-                                - LetterSpacing
-                                - LightningBolt
-                                - LineHeight
-                                - Link1
-                                - Link2
-                                - LinkBreak1
-                                - LinkBreak2
-                                - LinkNone1
-                                - LinkNone2
-                                - LinkedInLogo
-                                - ListBullet
-                                - LockClosed
-                                - LockOpen1
-                                - LockOpen2
-                                - Loop
-                                - MagicWand
-                                - MagnifyingGlass
-                                - Margin
-                                - MaskOff
-                                - MaskOn
-                                - Minus
-                                - MinusCircled
-                                - Mix
-                                - MixerHorizontal
-                                - MixerVertical
-                                - Mobile
-                                - ModulzLogo
-                                - Moon
-                                - Move
-                                - NotionLogo
-                                - Opacity
-                                - OpenInNewWindow
-                                - Overline
-                                - Padding
-                                - PaperPlane
-                                - Pause
-                                - Pencil1
-                                - Pencil2
-                                - Person
-                                - PieChart
-                                - Pilcrow
-                                - PinBottom
-                                - PinLeft
-                                - PinRight
-                                - PinTop
-                                - Play
-                                - Plus
-                                - PlusCircled
-                                - QuestionMark
-                                - QuestionMarkCircled
-                                - Quote
-                                - Radiobutton
-                                - Reader
-                                - Reload
-                                - Reset
-                                - Resume
-                                - Rocket
-                                - RotateCounterClockwise
-                                - RowSpacing
-                                - Rows
-                                - RulerHorizontal
-                                - RulerSquare
-                                - Scissors
-                                - Section
-                                - SewingPin
-                                - SewingPinFilled
-                                - Shadow
-                                - ShadowInner
-                                - ShadowNone
-                                - ShadowOuter
-                                - Share1
-                                - Share2
-                                - Shuffle
-                                - Size
-                                - SketchLogo
-                                - Slash
-                                - Slider
-                                - SpaceBetweenHorizontally
-                                - SpaceBetweenVertically
-                                - SpaceEvenlyHorizontally
-                                - SpaceEvenlyVertically
-                                - SpeakerLoud
-                                - SpeakerModerate
-                                - SpeakerOff
-                                - SpeakerQuiet
-                                - Square
-                                - Stack
-                                - Star
-                                - StarFilled
-                                - StitchesLogo
-                                - Stop
-                                - Stopwatch
-                                - StretchHorizontally
-                                - StretchVertically
-                                - Strikethrough
-                                - Sun
-                                - Switch
-                                - Symbol
-                                - Table
-                                - Target
-                                - Text
-                                - TextAlignBottom
-                                - TextAlignCenter
-                                - TextAlignJustify
-                                - TextAlignLeft
-                                - TextAlignMiddle
-                                - TextAlignRight
-                                - TextAlignTop
-                                - TextNone
-                                - ThickArrowDown
-                                - ThickArrowLeft
-                                - ThickArrowRight
-                                - ThickArrowUp
-                                - Timer
-                                - Tokens
-                                - TrackNext
-                                - TrackPrevious
-                                - Transform
-                                - TransparencyGrid
-                                - Trash
-                                - TriangleDown
-                                - TriangleLeft
-                                - TriangleRight
-                                - TriangleUp
-                                - TwitterLogo
-                                - Underline
-                                - Update
-                                - Upload
-                                - Value
-                                - ValueNone
-                                - VercelLogo
-                                - Video
-                                - ViewGrid
-                                - ViewHorizontal
-                                - ViewNone
-                                - ViewVertical
-                                - Width
-                                - ZoomIn
-                                - ZoomOut
+                                - ''
+                                - _blank
+                            attributes:
+                              type:
+                                - Drupal\Core\Template\Attribute
+                                - object
+                              name: Attributes
+                          additionalProperties: false
+                      description: Call to action link.
+                      mandatorySchemas: true
+                      slots: []
+              schema:
+                type: object
+                minProperties: 1
+                # @todo Validate the property keys match the
+                #   '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in
+                #    https://drupal.org/i/3506543.
+                # @see \Drupal\Core\Plugin\Component::$machineName
+                additionalProperties:
+                  $ref: '#/components/schemas/Component'
   /xb/api/config/js_component:
     get:
       description: Collection of Code Components
@@ -1464,7 +1089,6 @@ components:
         - id
         - category
         - default_markup
-        - list
       properties:
         name:
           type: string
@@ -1512,12 +1136,6 @@ components:
         dynamic_prop_source_candidates:
           type: object
           description: the DynamicPropSources that match each
-        list:
-          type: string
-          label: Component list
-          description: >
-            The list in which this component should appear. A list is used to group components in the UI.
-        additionalProperties: false
     CodeComponent:
       title: Code Component
       type: object
@@ -1669,11 +1287,11 @@ components:
             - uri
             - id
           examples:
-            Logged in user:
-              name: 'Garrett Bobby Ferguson, Jr'
-              avatar: /sites/default/files/user-pictures/garrett.jpg
-              uri: /user/12
-              id: 12
+            - Logged in user:
+                name: 'Garrett Bobby Ferguson, Jr'
+                avatar: /sites/default/files/user-pictures/garrett.jpg
+                uri: /user/12
+                id: 12
           properties:
             name:
               type: string
-- 
GitLab


From b2f48f70850068c807f4d058ad22626c7f4f0a8d Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 13:51:46 +0000
Subject: [PATCH 03/30] Add libraries to openapi yml, source plugins, component
 entity and controller

---
 openapi.yml                                   | 177 +++++++++++-------
 .../ComponentSourceInterface.php              |   7 +
 src/Controller/ApiConfigControllers.php       |  21 ++-
 src/Entity/Component.php                      |   9 +-
 .../ComponentSource/BlockComponent.php        |   8 +
 .../ComponentSource/JsComponent.php           |   8 +
 .../SingleDirectoryComponent.php              |  17 ++
 7 files changed, 171 insertions(+), 76 deletions(-)

diff --git a/openapi.yml b/openapi.yml
index 2aa4d5d3b1..da1dfadc78 100644
--- a/openapi.yml
+++ b/openapi.yml
@@ -159,76 +159,100 @@ paths:
               examples:
                 singleAvailableComponent:
                   value:
-                    'sdc_test:my-cta':
-                      id: 'sdc_test:my-cta'
-                      name: Call to Action
-                      source: Module component
-                      category: Buttons
-                      transforms:
-                        text:
-                          extractMainPropertyName:
-                            mainPropertyName: value
-                        href:
-                          extractMainPropertyName:
-                            mainPropertyName: value
-                        target:
-                          passThrough: {  }
-                      metadata:
-                        path: >-
-                          core/modules/system/tests/modules/sdc_test/components/my-cta
-                        documentation: >-
-                          No documentation found. Add a README.md in your
-                          component directory.
-                        status: stable
-                        machineName: my-cta
+                    libraries:
+                      # Provided by Experience Builder
+                      #elements:
+                        #label: Elements
+                      # Provided by Modules
+                      extension_components:
+                        label: Block Components
+                      dynamic_components:
+                        # SDCs from the Active Theme + JavaScript Code Components
+                        label: Active Theme Components and Code Components
+                      primary_components:
+                        label: Components?
+                    components:
+                      'sdc_test:my-cta':
+                        id: 'sdc_test:my-cta'
                         name: Call to Action
-                        group: All Components
-                        schema:
-                          type: object
-                          required:
-                            - text
-                          properties:
-                            text:
-                              type:
-                                - string
-                                - object
-                              title: Title
-                              description: The title for the cta
-                              examples:
-                                - Press
-                                - Submit now
-                            href:
-                              type:
-                                - string
-                                - object
-                              title: URL
-                              format: uri
-                            target:
-                              type:
-                                - string
-                                - object
-                              title: Target
-                              enum:
-                                - ''
-                                - _blank
-                            attributes:
-                              type:
-                                - Drupal\Core\Template\Attribute
-                                - object
-                              name: Attributes
-                          additionalProperties: false
-                      description: Call to action link.
-                      mandatorySchemas: true
-                      slots: []
+                        source: Module component
+                        category: Buttons
+                        library: extension_components
+                        transforms:
+                          text:
+                            extractMainPropertyName:
+                              mainPropertyName: value
+                          href:
+                            extractMainPropertyName:
+                              mainPropertyName: value
+                          target:
+                            passThrough: {  }
+                        metadata:
+                          path: >-
+                            core/modules/system/tests/modules/sdc_test/components/my-cta
+                          documentation: >-
+                            No documentation found. Add a README.md in your
+                            component directory.
+                          status: stable
+                          machineName: my-cta
+                          name: Call to Action
+                          group: All Components
+                          schema:
+                            type: object
+                            required:
+                              - text
+                            properties:
+                              text:
+                                type:
+                                  - string
+                                  - object
+                                title: Title
+                                description: The title for the cta
+                                examples:
+                                  - Press
+                                  - Submit now
+                              href:
+                                type:
+                                  - string
+                                  - object
+                                title: URL
+                                format: uri
+                              target:
+                                type:
+                                  - string
+                                  - object
+                                title: Target
+                                enum:
+                                  - ''
+                                  - _blank
+                              attributes:
+                                type:
+                                  - Drupal\Core\Template\Attribute
+                                  - object
+                                name: Attributes
+                            additionalProperties: false
+                        description: Call to action link.
+                        mandatorySchemas: true
+                        slots: []
               schema:
                 type: object
-                minProperties: 1
-                # @todo Validate the property keys match the
-                #   '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in
-                #    https://drupal.org/i/3506543.
-                # @see \Drupal\Core\Plugin\Component::$machineName
-                additionalProperties:
-                  $ref: '#/components/schemas/Component'
+                properties:
+                  libraries:
+                    extension_components:
+                      $ref: '#/components/schemas/Library'
+                    dynamic_components:
+                      $ref: '#/components/schemas/Library'
+                    primary_components:
+                      $ref: '#/components/schemas/Library'
+                  components:
+                    type: object
+                    minProperties: 1
+                    # @todo Validate the property keys match the
+                    #   '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in
+                    #    https://drupal.org/i/3506543.
+                    # @see \Drupal\Core\Plugin\Component::$machineName
+                    additionalProperties:
+                      $ref: '#/components/schemas/Component'
   /xb/api/config/js_component:
     get:
       description: Collection of Code Components
@@ -1089,6 +1113,7 @@ components:
         - id
         - category
         - default_markup
+        - library
       properties:
         name:
           type: string
@@ -1099,6 +1124,18 @@ components:
         category:
           type: string
           description: The component category
+        library:
+          type: string
+          description: Library ID
+          enum:
+            # Provided by Experience Builder
+            - elements
+            # Provided by Modules
+            - extension_components
+            # Blocks
+            - dynamic_components
+            # SDCs from the Active Theme + JavaScript Code Components
+            - primary_components
         transforms:
           type: object
           description: Transform configuration
@@ -1263,6 +1300,12 @@ components:
           type: boolean
         isPublished:
           type: boolean
+    Library:
+      title: Library
+      type: object
+      properties:
+        label:
+          type: string
     AutoSaveEntry:
       title: Auto Save Entry
       type: object
diff --git a/src/ComponentSource/ComponentSourceInterface.php b/src/ComponentSource/ComponentSourceInterface.php
index 7e72bafdf9..9e3873da96 100644
--- a/src/ComponentSource/ComponentSourceInterface.php
+++ b/src/ComponentSource/ComponentSourceInterface.php
@@ -205,4 +205,11 @@ interface ComponentSourceInterface extends PluginInspectionInterface, Derivative
    */
   public function checkRequirements(): void;
 
+  /**
+   * Returns Library ID to be used for UI placement in FE.
+   *
+   * @return string
+   */
+  public function getLibraryId(): string;
+
 }
diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php
index 93b303a43f..5e69c9b054 100644
--- a/src/Controller/ApiConfigControllers.php
+++ b/src/Controller/ApiConfigControllers.php
@@ -114,8 +114,27 @@ final class ApiConfigControllers extends ApiControllerBase {
     if ($max_age !== Cache::PERMANENT) {
       $normalizations_cacheability->setCacheMaxAge(max($max_age, 3600));
     }
+    if ($xb_config_entity_type_id === 'component') {
+      $response = [
+        'libraries' => [
+          'extension_components' => [
+            'label' => 'Block Components',
+          ],
+          'dynamic_components' => [
+            'label' => 'Active Theme Components and Code Components',
+          ],
+          'primary_components' => [
+            'label' => 'Components',
+          ],
+        ],
+        'components' => $normalizations,
+      ];
+    }
+    else {
+      $response = $normalizations;
+    }
 
-    return (new CacheableJsonResponse($normalizations))
+    return (new CacheableJsonResponse($response))
       ->addCacheableDependency($query_cacheability)
       ->addCacheableDependency($normalizations_cacheability);
   }
diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index 2ab3d8bced..10a7ab4981 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -206,18 +206,11 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
     $component_config_entity_uuid = $this->uuid();
     $build['#prefix'] = Markup::create("<!-- xb-start-$component_config_entity_uuid -->");
     $build['#suffix'] = Markup::create("<!-- xb-end-$component_config_entity_uuid -->");
-
-    $info += [
-      'id' => $this->id(),
-      'name' => (string) $this->label(),
-      'category' => (string) $this->getCategory(),
-      'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'],
-    ];
-
     return ClientSideRepresentation::create(
       values: $info + [
         'id' => $this->id(),
         'name' => (string) $this->label(),
+        'library' => $this->getComponentSource()->getLibraryId(),
         'category' => (string) $this->getCategory(),
         'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'],
       ],
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
index b1a3de55a1..ea7a9943a1 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
@@ -47,6 +47,7 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto
 
   public const SOURCE_PLUGIN_ID = 'block';
   public const EXPLICIT_INPUT_NAME = 'settings';
+  public const LIBRARY_ID = 'extension_components';
 
   /**
    * Constructs a new BlockComponent.
@@ -358,4 +359,11 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto
     // @todo Move logic from experience_builder_block_alter here in https://www.drupal.org/project/experience_builder/issues/3491032
   }
 
+  /**
+   * @return string
+   */
+  public function getLibraryId(): string {
+    return self::LIBRARY_ID;
+  }
+
 }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
index 80772366eb..8a9b2a3f6a 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
@@ -25,6 +25,7 @@ use Drupal\experience_builder\Entity\JavaScriptComponent;
 final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase {
 
   public const SOURCE_PLUGIN_ID = 'js';
+  public const LIBRARY_ID = 'primary_components';
 
   /**
    * {@inheritdoc}
@@ -226,4 +227,11 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase
     );
   }
 
+  /**
+   * @return string
+   */
+  public function getLibraryId(): string {
+    return self::LIBRARY_ID;
+  }
+
 }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index 0ce82f540a..471233ef28 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -32,6 +32,8 @@ use Symfony\Component\Filesystem\Path;
 final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxComponentSourceBase implements UrlRewriteInterface {
 
   public const SOURCE_PLUGIN_ID = 'sdc';
+  public const LIBRARY_ID_THEME = 'extension_components';
+  public const LIBRARY_ID_EXTENSIONS = 'primary_components';
 
   /**
    * Constructs a new SingleDirectoryComponent.
@@ -310,4 +312,19 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon
     return \base_path() . $path;
   }
 
+  /**
+   * @return string
+   */
+  public function getLibraryId(): string {
+    $base_id = $this->getComponentPlugin()->getBaseId();
+    if ($this->themeHandler->themeExists($base_id)) {
+      return self::LIBRARY_ID_THEME;
+    }
+
+    if ($this->moduleHandler->moduleExists($base_id)) {
+      return self::LIBRARY_ID_EXTENSIONS;
+    }
+    throw new \InvalidArgumentException("Invalid component provider id '{$base_id}'");
+  }
+
 }
-- 
GitLab


From 753ae98ed88e718c1e2ef596ef1f5c047a1c539e Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 13:54:41 +0000
Subject: [PATCH 04/30] Revert dictionary changes

---
 dictionary.txt | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/dictionary.txt b/dictionary.txt
index 579baf9f7f..c1402f512b 100644
--- a/dictionary.txt
+++ b/dictionary.txt
@@ -59,7 +59,3 @@ cebe
 inwidget
 uuidv
 Linkit
-Modulz
-Pilcrow
-Vercel
-
-- 
GitLab


From 2c789e11bdb2bf793edbeed2b8fae378fccb9aa2 Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 13:55:59 +0000
Subject: [PATCH 05/30] Remove elements library from openapi for now

---
 openapi.yml | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/openapi.yml b/openapi.yml
index da1dfadc78..11deb4432e 100644
--- a/openapi.yml
+++ b/openapi.yml
@@ -160,9 +160,6 @@ paths:
                 singleAvailableComponent:
                   value:
                     libraries:
-                      # Provided by Experience Builder
-                      #elements:
-                        #label: Elements
                       # Provided by Modules
                       extension_components:
                         label: Block Components
@@ -1128,8 +1125,6 @@ components:
           type: string
           description: Library ID
           enum:
-            # Provided by Experience Builder
-            - elements
             # Provided by Modules
             - extension_components
             # Blocks
-- 
GitLab


From bc67a667b7289cda6e4f272386a991e83c5a8e13 Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 14:23:53 +0000
Subject: [PATCH 06/30] Reverting controller changes

---
 src/Controller/ApiConfigControllers.php | 21 +--------------------
 1 file changed, 1 insertion(+), 20 deletions(-)

diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php
index 5e69c9b054..93b303a43f 100644
--- a/src/Controller/ApiConfigControllers.php
+++ b/src/Controller/ApiConfigControllers.php
@@ -114,27 +114,8 @@ final class ApiConfigControllers extends ApiControllerBase {
     if ($max_age !== Cache::PERMANENT) {
       $normalizations_cacheability->setCacheMaxAge(max($max_age, 3600));
     }
-    if ($xb_config_entity_type_id === 'component') {
-      $response = [
-        'libraries' => [
-          'extension_components' => [
-            'label' => 'Block Components',
-          ],
-          'dynamic_components' => [
-            'label' => 'Active Theme Components and Code Components',
-          ],
-          'primary_components' => [
-            'label' => 'Components',
-          ],
-        ],
-        'components' => $normalizations,
-      ];
-    }
-    else {
-      $response = $normalizations;
-    }
 
-    return (new CacheableJsonResponse($response))
+    return (new CacheableJsonResponse($normalizations))
       ->addCacheableDependency($query_cacheability)
       ->addCacheableDependency($normalizations_cacheability);
   }
-- 
GitLab


From d307e188fc1c484243e688a2ea0c195ecf7394db Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 14:26:17 +0000
Subject: [PATCH 07/30] Undoing config list chape change

---
 openapi.yml | 162 ++++++++++++++++++++++------------------------------
 1 file changed, 68 insertions(+), 94 deletions(-)

diff --git a/openapi.yml b/openapi.yml
index 11deb4432e..9ccca22ab6 100644
--- a/openapi.yml
+++ b/openapi.yml
@@ -159,97 +159,77 @@ paths:
               examples:
                 singleAvailableComponent:
                   value:
-                    libraries:
-                      # Provided by Modules
-                      extension_components:
-                        label: Block Components
-                      dynamic_components:
-                        # SDCs from the Active Theme + JavaScript Code Components
-                        label: Active Theme Components and Code Components
-                      primary_components:
-                        label: Components?
-                    components:
-                      'sdc_test:my-cta':
-                        id: 'sdc_test:my-cta'
+                    'sdc_test:my-cta':
+                      id: 'sdc_test:my-cta'
+                      name: Call to Action
+                      source: Module component
+                      category: Buttons
+                      library: extension_components
+                      transforms:
+                        text:
+                          extractMainPropertyName:
+                            mainPropertyName: value
+                        href:
+                          extractMainPropertyName:
+                            mainPropertyName: value
+                        target:
+                          passThrough: {  }
+                      metadata:
+                        path: >-
+                          core/modules/system/tests/modules/sdc_test/components/my-cta
+                        documentation: >-
+                          No documentation found. Add a README.md in your
+                          component directory.
+                        status: stable
+                        machineName: my-cta
                         name: Call to Action
-                        source: Module component
-                        category: Buttons
-                        library: extension_components
-                        transforms:
-                          text:
-                            extractMainPropertyName:
-                              mainPropertyName: value
-                          href:
-                            extractMainPropertyName:
-                              mainPropertyName: value
-                          target:
-                            passThrough: {  }
-                        metadata:
-                          path: >-
-                            core/modules/system/tests/modules/sdc_test/components/my-cta
-                          documentation: >-
-                            No documentation found. Add a README.md in your
-                            component directory.
-                          status: stable
-                          machineName: my-cta
-                          name: Call to Action
-                          group: All Components
-                          schema:
-                            type: object
-                            required:
-                              - text
-                            properties:
-                              text:
-                                type:
-                                  - string
-                                  - object
-                                title: Title
-                                description: The title for the cta
-                                examples:
-                                  - Press
-                                  - Submit now
-                              href:
-                                type:
-                                  - string
-                                  - object
-                                title: URL
-                                format: uri
-                              target:
-                                type:
-                                  - string
-                                  - object
-                                title: Target
-                                enum:
-                                  - ''
-                                  - _blank
-                              attributes:
-                                type:
-                                  - Drupal\Core\Template\Attribute
-                                  - object
-                                name: Attributes
-                            additionalProperties: false
-                        description: Call to action link.
-                        mandatorySchemas: true
-                        slots: []
+                        group: All Components
+                        schema:
+                          type: object
+                          required:
+                            - text
+                          properties:
+                            text:
+                              type:
+                                - string
+                                - object
+                              title: Title
+                              description: The title for the cta
+                              examples:
+                                - Press
+                                - Submit now
+                            href:
+                              type:
+                                - string
+                                - object
+                              title: URL
+                              format: uri
+                            target:
+                              type:
+                                - string
+                                - object
+                              title: Target
+                              enum:
+                                - ''
+                                - _blank
+                            attributes:
+                              type:
+                                - Drupal\Core\Template\Attribute
+                                - object
+                              name: Attributes
+                          additionalProperties: false
+                      description: Call to action link.
+                      mandatorySchemas: true
+                      slots: []
               schema:
                 type: object
-                properties:
-                  libraries:
-                    extension_components:
-                      $ref: '#/components/schemas/Library'
-                    dynamic_components:
-                      $ref: '#/components/schemas/Library'
-                    primary_components:
-                      $ref: '#/components/schemas/Library'
-                  components:
-                    type: object
-                    minProperties: 1
-                    # @todo Validate the property keys match the
-                    #   '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in
-                    #    https://drupal.org/i/3506543.
-                    # @see \Drupal\Core\Plugin\Component::$machineName
-                    additionalProperties:
-                      $ref: '#/components/schemas/Component'
+                minProperties: 1
+                # @todo Validate the property keys match the
+                #   '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in
+                #    https://drupal.org/i/3506543.
+                # @see \Drupal\Core\Plugin\Component::$machineName
+                additionalProperties:
+                  $ref: '#/components/schemas/Component'
   /xb/api/config/js_component:
     get:
       description: Collection of Code Components
@@ -1295,12 +1275,6 @@ components:
           type: boolean
         isPublished:
           type: boolean
-    Library:
-      title: Library
-      type: object
-      properties:
-        label:
-          type: string
     AutoSaveEntry:
       title: Auto Save Entry
       type: object
-- 
GitLab


From 17b4de029c3ffe6a7030ef2b73aa9319e63c5863 Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 15:41:07 +0000
Subject: [PATCH 08/30] Switch to using enum for library id

---
 .../ComponentSourceInterface.php              |  5 ++--
 src/Controller/LibraryIdEnum.php              | 25 +++++++++++++++++++
 src/Entity/Component.php                      |  2 +-
 .../ComponentSource/BlockComponent.php        |  8 +++---
 .../ComponentSource/JsComponent.php           |  8 +++---
 .../SingleDirectoryComponent.php              | 11 ++++----
 6 files changed, 42 insertions(+), 17 deletions(-)
 create mode 100644 src/Controller/LibraryIdEnum.php

diff --git a/src/ComponentSource/ComponentSourceInterface.php b/src/ComponentSource/ComponentSourceInterface.php
index 9e3873da96..7b1381bc01 100644
--- a/src/ComponentSource/ComponentSourceInterface.php
+++ b/src/ComponentSource/ComponentSourceInterface.php
@@ -14,6 +14,7 @@ use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Drupal\Core\Plugin\PluginFormInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\experience_builder\Controller\LibraryIdEnum;
 use Drupal\experience_builder\Entity\Component;
 use Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem;
 use Symfony\Component\Validator\ConstraintViolationListInterface;
@@ -208,8 +209,8 @@ interface ComponentSourceInterface extends PluginInspectionInterface, Derivative
   /**
    * Returns Library ID to be used for UI placement in FE.
    *
-   * @return string
+   * @return \Drupal\experience_builder\Controller\LibraryIdEnum
    */
-  public function getLibraryId(): string;
+  public function getLibraryId(): LibraryIdEnum;
 
 }
diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryIdEnum.php
new file mode 100644
index 0000000000..73d7b70007
--- /dev/null
+++ b/src/Controller/LibraryIdEnum.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\experience_builder\Controller;
+
+/**
+ * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15988043
+ * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15989118
+ */
+enum LibraryIdEnum: string {
+  case ModuleComponents = 'extension_components';
+  case BlockComponent = 'block_components';
+  case ActiveThemeComponents = 'theme_components';
+  case JsComponents = 'js_components';
+
+  public function getId(): string {
+    return match($this) {
+      self::ModuleComponents => 'extension_components',
+      self::BlockComponent => 'dynamic_components',
+      self::ActiveThemeComponents, self::JsComponents => 'primary_components',
+    };
+  }
+
+}
diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index 10a7ab4981..47032472ee 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -210,7 +210,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
       values: $info + [
         'id' => $this->id(),
         'name' => (string) $this->label(),
-        'library' => $this->getComponentSource()->getLibraryId(),
+        'library' => $this->getComponentSource()->getLibraryId()->getId(),
         'category' => (string) $this->getCategory(),
         'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'],
       ],
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
index ea7a9943a1..6024e352ae 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
@@ -22,6 +22,7 @@ use Drupal\Core\TypedData\Plugin\DataType\BooleanData;
 use Drupal\Core\TypedData\TypedDataInterface;
 use Drupal\experience_builder\Attribute\ComponentSource;
 use Drupal\experience_builder\ComponentSource\ComponentSourceBase;
+use Drupal\experience_builder\Controller\LibraryIdEnum;
 use Drupal\experience_builder\Entity\Component;
 use Drupal\experience_builder\Entity\Component as ComponentEntity;
 use Drupal\experience_builder\MissingComponentInputsException;
@@ -47,7 +48,6 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto
 
   public const SOURCE_PLUGIN_ID = 'block';
   public const EXPLICIT_INPUT_NAME = 'settings';
-  public const LIBRARY_ID = 'extension_components';
 
   /**
    * Constructs a new BlockComponent.
@@ -360,10 +360,10 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto
   }
 
   /**
-   * @return string
+   * {@inheritdoc}
    */
-  public function getLibraryId(): string {
-    return self::LIBRARY_ID;
+  public function getLibraryId(): LibraryIdEnum {
+    return LibraryIdEnum::BlockComponent;
   }
 
 }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
index 8a9b2a3f6a..6f488604ab 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
@@ -11,6 +11,7 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\experience_builder\Attribute\ComponentSource;
 use Drupal\experience_builder\ComponentDoesNotMeetRequirementsException;
 use Drupal\experience_builder\ComponentMetadataRequirementsChecker;
+use Drupal\experience_builder\Controller\LibraryIdEnum;
 use Drupal\experience_builder\Entity\Component as ComponentEntity;
 use Drupal\experience_builder\Entity\ComponentInterface;
 use Drupal\experience_builder\Entity\JavaScriptComponent;
@@ -25,7 +26,6 @@ use Drupal\experience_builder\Entity\JavaScriptComponent;
 final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase {
 
   public const SOURCE_PLUGIN_ID = 'js';
-  public const LIBRARY_ID = 'primary_components';
 
   /**
    * {@inheritdoc}
@@ -228,10 +228,10 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase
   }
 
   /**
-   * @return string
+   * {@inheritdoc}
    */
-  public function getLibraryId(): string {
-    return self::LIBRARY_ID;
+  public function getLibraryId(): LibraryIdEnum {
+    return LibraryIdEnum::JsComponents;
   }
 
 }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index 471233ef28..c4e8eabcb8 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -16,6 +16,7 @@ use Drupal\Core\Theme\ComponentPluginManager;
 use Drupal\Core\Theme\ExtensionType;
 use Drupal\experience_builder\Attribute\ComponentSource;
 use Drupal\experience_builder\ComponentSource\UrlRewriteInterface;
+use Drupal\experience_builder\Controller\LibraryIdEnum;
 use Drupal\experience_builder\Entity\Component as ComponentEntity;
 use Drupal\experience_builder\Plugin\ComponentPluginManager as XbComponentPluginManager;
 use Drupal\experience_builder\ShapeMatcher\FieldForComponentSuggester;
@@ -32,8 +33,6 @@ use Symfony\Component\Filesystem\Path;
 final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxComponentSourceBase implements UrlRewriteInterface {
 
   public const SOURCE_PLUGIN_ID = 'sdc';
-  public const LIBRARY_ID_THEME = 'extension_components';
-  public const LIBRARY_ID_EXTENSIONS = 'primary_components';
 
   /**
    * Constructs a new SingleDirectoryComponent.
@@ -313,16 +312,16 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon
   }
 
   /**
-   * @return string
+   * {@inheritdoc}
    */
-  public function getLibraryId(): string {
+  public function getLibraryId(): LibraryIdEnum {
     $base_id = $this->getComponentPlugin()->getBaseId();
     if ($this->themeHandler->themeExists($base_id)) {
-      return self::LIBRARY_ID_THEME;
+      return LibraryIdEnum::ActiveThemeComponents;
     }
 
     if ($this->moduleHandler->moduleExists($base_id)) {
-      return self::LIBRARY_ID_EXTENSIONS;
+      return LibraryIdEnum::ModuleComponents;
     }
     throw new \InvalidArgumentException("Invalid component provider id '{$base_id}'");
   }
-- 
GitLab


From 75919ac3ff2655bc1731e6007fd3b49b425af09f Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 15:44:14 +0000
Subject: [PATCH 09/30] Minor rename

---
 src/Controller/LibraryIdEnum.php                              | 4 ++--
 .../ComponentSource/SingleDirectoryComponent.php              | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryIdEnum.php
index 73d7b70007..2b6838b425 100644
--- a/src/Controller/LibraryIdEnum.php
+++ b/src/Controller/LibraryIdEnum.php
@@ -11,14 +11,14 @@ namespace Drupal\experience_builder\Controller;
 enum LibraryIdEnum: string {
   case ModuleComponents = 'extension_components';
   case BlockComponent = 'block_components';
-  case ActiveThemeComponents = 'theme_components';
+  case ThemeComponents = 'theme_components';
   case JsComponents = 'js_components';
 
   public function getId(): string {
     return match($this) {
       self::ModuleComponents => 'extension_components',
       self::BlockComponent => 'dynamic_components',
-      self::ActiveThemeComponents, self::JsComponents => 'primary_components',
+      self::ThemeComponents, self::JsComponents => 'primary_components',
     };
   }
 
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index c4e8eabcb8..1e6835de41 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -317,7 +317,7 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon
   public function getLibraryId(): LibraryIdEnum {
     $base_id = $this->getComponentPlugin()->getBaseId();
     if ($this->themeHandler->themeExists($base_id)) {
-      return LibraryIdEnum::ActiveThemeComponents;
+      return LibraryIdEnum::ThemeComponents;
     }
 
     if ($this->moduleHandler->moduleExists($base_id)) {
-- 
GitLab


From eaec83639b3d0558b314c7ab1964fd063854bb46 Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 16:59:45 +0000
Subject: [PATCH 10/30] Enum refactor

---
 src/Controller/LibraryIdEnum.php                  | 15 +++------------
 src/Entity/Component.php                          |  2 +-
 .../ComponentSource/BlockComponent.php            |  2 +-
 .../ComponentSource/JsComponent.php               |  2 +-
 .../ComponentSource/SingleDirectoryComponent.php  |  8 +++-----
 5 files changed, 9 insertions(+), 20 deletions(-)

diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryIdEnum.php
index 2b6838b425..aa33943467 100644
--- a/src/Controller/LibraryIdEnum.php
+++ b/src/Controller/LibraryIdEnum.php
@@ -9,17 +9,8 @@ namespace Drupal\experience_builder\Controller;
  * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15989118
  */
 enum LibraryIdEnum: string {
-  case ModuleComponents = 'extension_components';
-  case BlockComponent = 'block_components';
-  case ThemeComponents = 'theme_components';
-  case JsComponents = 'js_components';
-
-  public function getId(): string {
-    return match($this) {
-      self::ModuleComponents => 'extension_components',
-      self::BlockComponent => 'dynamic_components',
-      self::ThemeComponents, self::JsComponents => 'primary_components',
-    };
-  }
+  case ExtensionComponents = 'extension_components';
+  case DynamicComponents = 'dynamic_components';
+  case PrimaryComponents = 'primary_components';
 
 }
diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index 47032472ee..10a7ab4981 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -210,7 +210,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
       values: $info + [
         'id' => $this->id(),
         'name' => (string) $this->label(),
-        'library' => $this->getComponentSource()->getLibraryId()->getId(),
+        'library' => $this->getComponentSource()->getLibraryId(),
         'category' => (string) $this->getCategory(),
         'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'],
       ],
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
index 6024e352ae..e7a87ad712 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
@@ -363,7 +363,7 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto
    * {@inheritdoc}
    */
   public function getLibraryId(): LibraryIdEnum {
-    return LibraryIdEnum::BlockComponent;
+    return LibraryIdEnum::DynamicComponents;
   }
 
 }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
index 6f488604ab..b7f8aadc52 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
@@ -231,7 +231,7 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase
    * {@inheritdoc}
    */
   public function getLibraryId(): LibraryIdEnum {
-    return LibraryIdEnum::JsComponents;
+    return LibraryIdEnum::PrimaryComponents;
   }
 
 }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index 1e6835de41..85a17e35ea 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -316,14 +316,12 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon
    */
   public function getLibraryId(): LibraryIdEnum {
     $base_id = $this->getComponentPlugin()->getBaseId();
-    if ($this->themeHandler->themeExists($base_id)) {
-      return LibraryIdEnum::ThemeComponents;
-    }
 
     if ($this->moduleHandler->moduleExists($base_id)) {
-      return LibraryIdEnum::ModuleComponents;
+      return LibraryIdEnum::ExtensionComponents;
     }
-    throw new \InvalidArgumentException("Invalid component provider id '{$base_id}'");
+
+    return LibraryIdEnum::PrimaryComponents;
   }
 
 }
-- 
GitLab


From e11acc543e1b6c93119247ad17a31d7d38262620 Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 17:14:15 +0000
Subject: [PATCH 11/30] Exclude non-default theme components from endpoint

---
 src/Controller/ApiConfigControllers.php                     | 4 ++++
 src/Controller/LibraryIdEnum.php                            | 1 +
 .../ComponentSource/SingleDirectoryComponent.php            | 6 +++++-
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php
index 93b303a43f..5df9811f2a 100644
--- a/src/Controller/ApiConfigControllers.php
+++ b/src/Controller/ApiConfigControllers.php
@@ -95,6 +95,10 @@ final class ApiConfigControllers extends ApiControllerBase {
     $normalizations_cacheability = new CacheableMetadata();
     foreach ($config_entities as $key => &$entity) {
       $representation = $this->normalize($entity);
+      // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374
+      if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryIdEnum::None) {
+        continue;
+      }
       $normalizations[$key] = $representation->values;
       $normalizations_cacheability->addCacheableDependency($representation);
     }
diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryIdEnum.php
index aa33943467..8b4fdb3270 100644
--- a/src/Controller/LibraryIdEnum.php
+++ b/src/Controller/LibraryIdEnum.php
@@ -12,5 +12,6 @@ enum LibraryIdEnum: string {
   case ExtensionComponents = 'extension_components';
   case DynamicComponents = 'dynamic_components';
   case PrimaryComponents = 'primary_components';
+  case None = '';
 
 }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index 85a17e35ea..006a051c28 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -321,7 +321,11 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon
       return LibraryIdEnum::ExtensionComponents;
     }
 
-    return LibraryIdEnum::PrimaryComponents;
+    if ($this->themeHandler->getDefault() === $base_id) {
+      return LibraryIdEnum::PrimaryComponents;
+    }
+
+    return LibraryIdEnum::None;
   }
 
 }
-- 
GitLab


From a5329c48e2fa44e7053b157410afd133ff3db364 Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 17:23:10 +0000
Subject: [PATCH 12/30] Add Elements

---
 openapi.yml                                                     | 2 ++
 src/Controller/LibraryIdEnum.php                                | 1 +
 .../ComponentSource/SingleDirectoryComponent.php                | 2 +-
 3 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/openapi.yml b/openapi.yml
index 9ccca22ab6..7df1084fd3 100644
--- a/openapi.yml
+++ b/openapi.yml
@@ -1105,6 +1105,8 @@ components:
           type: string
           description: Library ID
           enum:
+            # Provided by Experience Builder
+            - elements
             # Provided by Modules
             - extension_components
             # Blocks
diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryIdEnum.php
index 8b4fdb3270..145bd390b4 100644
--- a/src/Controller/LibraryIdEnum.php
+++ b/src/Controller/LibraryIdEnum.php
@@ -9,6 +9,7 @@ namespace Drupal\experience_builder\Controller;
  * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15989118
  */
 enum LibraryIdEnum: string {
+  case Elements = 'elements';
   case ExtensionComponents = 'extension_components';
   case DynamicComponents = 'dynamic_components';
   case PrimaryComponents = 'primary_components';
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index 006a051c28..d2b22766dc 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -318,7 +318,7 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon
     $base_id = $this->getComponentPlugin()->getBaseId();
 
     if ($this->moduleHandler->moduleExists($base_id)) {
-      return LibraryIdEnum::ExtensionComponents;
+      return $base_id === 'experience_builder' ? LibraryIdEnum::Elements : LibraryIdEnum::ExtensionComponents;
     }
 
     if ($this->themeHandler->getDefault() === $base_id) {
-- 
GitLab


From a17e09e8e68bd5a0d315202ddd885f664b7bbbd3 Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 18:12:51 +0000
Subject: [PATCH 13/30] Rename enum

---
 src/ComponentSource/ComponentSourceInterface.php       |  8 ++++----
 src/Controller/ApiConfigControllers.php                |  2 +-
 src/Controller/{LibraryIdEnum.php => LibraryEnum.php}  |  2 +-
 src/Entity/Component.php                               |  2 +-
 .../ComponentSource/BlockComponent.php                 |  6 +++---
 .../ExperienceBuilder/ComponentSource/JsComponent.php  |  6 +++---
 .../ComponentSource/SingleDirectoryComponent.php       | 10 +++++-----
 7 files changed, 18 insertions(+), 18 deletions(-)
 rename src/Controller/{LibraryIdEnum.php => LibraryEnum.php} (94%)

diff --git a/src/ComponentSource/ComponentSourceInterface.php b/src/ComponentSource/ComponentSourceInterface.php
index 7b1381bc01..d740e57fbb 100644
--- a/src/ComponentSource/ComponentSourceInterface.php
+++ b/src/ComponentSource/ComponentSourceInterface.php
@@ -14,7 +14,7 @@ use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Drupal\Core\Plugin\PluginFormInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
-use Drupal\experience_builder\Controller\LibraryIdEnum;
+use Drupal\experience_builder\Controller\LibraryEnum;
 use Drupal\experience_builder\Entity\Component;
 use Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem;
 use Symfony\Component\Validator\ConstraintViolationListInterface;
@@ -207,10 +207,10 @@ interface ComponentSourceInterface extends PluginInspectionInterface, Derivative
   public function checkRequirements(): void;
 
   /**
-   * Returns Library ID to be used for UI placement in FE.
+   * Returns Library enum.
    *
-   * @return \Drupal\experience_builder\Controller\LibraryIdEnum
+   * @return \Drupal\experience_builder\Controller\LibraryEnum
    */
-  public function getLibraryId(): LibraryIdEnum;
+  public function getLibrary(): LibraryEnum;
 
 }
diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php
index 5df9811f2a..51a2ed5bfc 100644
--- a/src/Controller/ApiConfigControllers.php
+++ b/src/Controller/ApiConfigControllers.php
@@ -96,7 +96,7 @@ final class ApiConfigControllers extends ApiControllerBase {
     foreach ($config_entities as $key => &$entity) {
       $representation = $this->normalize($entity);
       // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374
-      if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryIdEnum::None) {
+      if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryEnum::None) {
         continue;
       }
       $normalizations[$key] = $representation->values;
diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryEnum.php
similarity index 94%
rename from src/Controller/LibraryIdEnum.php
rename to src/Controller/LibraryEnum.php
index 145bd390b4..feaabc0675 100644
--- a/src/Controller/LibraryIdEnum.php
+++ b/src/Controller/LibraryEnum.php
@@ -8,7 +8,7 @@ namespace Drupal\experience_builder\Controller;
  * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15988043
  * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15989118
  */
-enum LibraryIdEnum: string {
+enum LibraryEnum: string {
   case Elements = 'elements';
   case ExtensionComponents = 'extension_components';
   case DynamicComponents = 'dynamic_components';
diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index 10a7ab4981..13f756b381 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -210,7 +210,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
       values: $info + [
         'id' => $this->id(),
         'name' => (string) $this->label(),
-        'library' => $this->getComponentSource()->getLibraryId(),
+        'library' => $this->getComponentSource()->getLibrary(),
         'category' => (string) $this->getCategory(),
         'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'],
       ],
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
index e7a87ad712..d3f70453e4 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
@@ -22,7 +22,7 @@ use Drupal\Core\TypedData\Plugin\DataType\BooleanData;
 use Drupal\Core\TypedData\TypedDataInterface;
 use Drupal\experience_builder\Attribute\ComponentSource;
 use Drupal\experience_builder\ComponentSource\ComponentSourceBase;
-use Drupal\experience_builder\Controller\LibraryIdEnum;
+use Drupal\experience_builder\Controller\LibraryEnum;
 use Drupal\experience_builder\Entity\Component;
 use Drupal\experience_builder\Entity\Component as ComponentEntity;
 use Drupal\experience_builder\MissingComponentInputsException;
@@ -362,8 +362,8 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto
   /**
    * {@inheritdoc}
    */
-  public function getLibraryId(): LibraryIdEnum {
-    return LibraryIdEnum::DynamicComponents;
+  public function getLibrary(): LibraryEnum {
+    return LibraryEnum::DynamicComponents;
   }
 
 }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
index b7f8aadc52..460814d25f 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
@@ -11,7 +11,7 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\experience_builder\Attribute\ComponentSource;
 use Drupal\experience_builder\ComponentDoesNotMeetRequirementsException;
 use Drupal\experience_builder\ComponentMetadataRequirementsChecker;
-use Drupal\experience_builder\Controller\LibraryIdEnum;
+use Drupal\experience_builder\Controller\LibraryEnum;
 use Drupal\experience_builder\Entity\Component as ComponentEntity;
 use Drupal\experience_builder\Entity\ComponentInterface;
 use Drupal\experience_builder\Entity\JavaScriptComponent;
@@ -230,8 +230,8 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase
   /**
    * {@inheritdoc}
    */
-  public function getLibraryId(): LibraryIdEnum {
-    return LibraryIdEnum::PrimaryComponents;
+  public function getLibrary(): LibraryEnum {
+    return LibraryEnum::PrimaryComponents;
   }
 
 }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index d2b22766dc..295824e4d9 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -16,7 +16,7 @@ use Drupal\Core\Theme\ComponentPluginManager;
 use Drupal\Core\Theme\ExtensionType;
 use Drupal\experience_builder\Attribute\ComponentSource;
 use Drupal\experience_builder\ComponentSource\UrlRewriteInterface;
-use Drupal\experience_builder\Controller\LibraryIdEnum;
+use Drupal\experience_builder\Controller\LibraryEnum;
 use Drupal\experience_builder\Entity\Component as ComponentEntity;
 use Drupal\experience_builder\Plugin\ComponentPluginManager as XbComponentPluginManager;
 use Drupal\experience_builder\ShapeMatcher\FieldForComponentSuggester;
@@ -314,18 +314,18 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon
   /**
    * {@inheritdoc}
    */
-  public function getLibraryId(): LibraryIdEnum {
+  public function getLibrary(): LibraryEnum {
     $base_id = $this->getComponentPlugin()->getBaseId();
 
     if ($this->moduleHandler->moduleExists($base_id)) {
-      return $base_id === 'experience_builder' ? LibraryIdEnum::Elements : LibraryIdEnum::ExtensionComponents;
+      return $base_id === 'experience_builder' ? LibraryEnum::Elements : LibraryEnum::ExtensionComponents;
     }
 
     if ($this->themeHandler->getDefault() === $base_id) {
-      return LibraryIdEnum::PrimaryComponents;
+      return LibraryEnum::PrimaryComponents;
     }
 
-    return LibraryIdEnum::None;
+    return LibraryEnum::None;
   }
 
 }
-- 
GitLab


From 7d06253991589143714ce066493d9941db726522 Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 18:15:37 +0000
Subject: [PATCH 14/30] Added additional inline docs

---
 src/Controller/ApiConfigControllers.php                        | 2 ++
 .../ComponentSource/SingleDirectoryComponent.php               | 3 +++
 2 files changed, 5 insertions(+)

diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php
index 51a2ed5bfc..3ddc5161ca 100644
--- a/src/Controller/ApiConfigControllers.php
+++ b/src/Controller/ApiConfigControllers.php
@@ -95,6 +95,8 @@ final class ApiConfigControllers extends ApiControllerBase {
     $normalizations_cacheability = new CacheableMetadata();
     foreach ($config_entities as $key => &$entity) {
       $representation = $this->normalize($entity);
+      // If component is provided by non-default theme it should be excluded
+      // from the endpoint output.
       // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374
       if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryEnum::None) {
         continue;
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index 295824e4d9..3d6d97617e 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -325,6 +325,9 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon
       return LibraryEnum::PrimaryComponents;
     }
 
+    // If component is provided by non-default theme it should be excluded
+    // from the endpoint output.
+    // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374
     return LibraryEnum::None;
   }
 
-- 
GitLab


From 7c687108cef45f7f322005e7820f18350793cdfc Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 18:17:53 +0000
Subject: [PATCH 15/30] Added additional inline docs

---
 src/Controller/ApiConfigControllers.php                       | 1 +
 .../ComponentSource/SingleDirectoryComponent.php              | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php
index 3ddc5161ca..c3d618ac8f 100644
--- a/src/Controller/ApiConfigControllers.php
+++ b/src/Controller/ApiConfigControllers.php
@@ -97,6 +97,7 @@ final class ApiConfigControllers extends ApiControllerBase {
       $representation = $this->normalize($entity);
       // If component is provided by non-default theme it should be excluded
       // from the endpoint output.
+      // @see \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\SingleDirectoryComponent::getLibrary()
       // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374
       if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryEnum::None) {
         continue;
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index 3d6d97617e..51ffbb6bf1 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -325,8 +325,8 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon
       return LibraryEnum::PrimaryComponents;
     }
 
-    // If component is provided by non-default theme it should be excluded
-    // from the endpoint output.
+    // If component is provided by non-default theme it should not be assigned
+    // 'primary_components' library value.
     // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374
     return LibraryEnum::None;
   }
-- 
GitLab


From ebaa04f48e1f00be5a22bc80e4d19d2b26606331 Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Thu, 20 Feb 2025 18:22:34 +0000
Subject: [PATCH 16/30] Adding ->value

---
 src/Entity/Component.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index 13f756b381..aae0c016b0 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -210,7 +210,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
       values: $info + [
         'id' => $this->id(),
         'name' => (string) $this->label(),
-        'library' => $this->getComponentSource()->getLibrary(),
+        'library' => $this->getComponentSource()->getLibrary()->value,
         'category' => (string) $this->getCategory(),
         'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'],
       ],
-- 
GitLab


From 523e39540de5ad62af948254d3e04c11b17dd0e1 Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Thu, 20 Feb 2025 18:58:04 +0100
Subject: [PATCH 17/30] Add missing metadata: whether a `ComponentSource`
 supports implicit inputs or not.

---
 docs/components.md                                       | 9 ++++++++-
 src/Attribute/ComponentSource.php                        | 1 +
 .../ExperienceBuilder/ComponentSource/BlockComponent.php | 5 ++++-
 .../ExperienceBuilder/ComponentSource/JsComponent.php    | 3 ++-
 .../ComponentSource/SingleDirectoryComponent.php         | 3 ++-
 5 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/docs/components.md b/docs/components.md
index c90582957d..6744246213 100644
--- a/docs/components.md
+++ b/docs/components.md
@@ -93,7 +93,12 @@ Therefore, it only makes sense to surface _block plugins_ as XB `component`s.
 defined  using config schema (`type: block.settings.<PLUGIN ID>`). Defaults are present as the
 `::defaultConfiguration()` method  on the PHP plugin class.
 
-`Block` `component`s specify the accepted explicit inputs
+`Block` `component`s DO accept implicit inputs, in two ways even:
+1. Logic in the block plugin can fetch data — through database queries, HTTP requests, anything.
+2. Contexts. Not yet supported. (⚠️ handling contexts is still TBD in [#3485502](https://www.drupal.org/project/experience_builder/issues/3485502))
+
+`Block` `component`s specify the accepted explicit inputs. They typically allow influencing the logic in the block
+plugin. These explicit inputs can hence be seen as knobs and levers to adjust what the underlying block plugin does.
 
 `Block` DOES provide an input UX (`BlockPluginInterface::buildConfigurationForm()`), so its `Component Source Plugin`
 simply reuses that.
@@ -128,6 +133,8 @@ refer to "blocks" and not "block plugins", under the hood, they actually _are_ b
 3.1.1 above](#3.1.1)). See [section 3.2 `JavaScriptComponent config entity` in the `XB Config Management`
 doc](config-management.md#3.2) for all details.
 
+`JS` `component`s DO NOT accept implicit inputs.
+
 `JS` DOES NOT provide an input UX, so its `Component Source Plugin` must do so on its behalf; and does so by matching
 available field types against the JSON schema of its explicit inputs ("props"). For details, see the [`XB Shape Matching
 into Field Types` doc](shape-matching-into-field-types.md). (It shares this infrastructure with the `SDC` `Component
diff --git a/src/Attribute/ComponentSource.php b/src/Attribute/ComponentSource.php
index f714ac9611..0c3e176000 100644
--- a/src/Attribute/ComponentSource.php
+++ b/src/Attribute/ComponentSource.php
@@ -25,6 +25,7 @@ final class ComponentSource extends Plugin {
   public function __construct(
     public readonly string $id,
     public readonly TranslatableMarkup $label,
+    public readonly bool $supportsImplicitInputs,
     public readonly ?string $deriver = NULL,
   ) {
   }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
index d3f70453e4..64aa781c74 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
@@ -40,7 +40,10 @@ use Symfony\Component\Validator\ConstraintViolationListInterface;
  */
 #[ComponentSource(
   id: self::SOURCE_PLUGIN_ID,
-  label: new TranslatableMarkup('Blocks')
+  label: new TranslatableMarkup('Blocks'),
+  // While XB does not support context mappings yet, Block plugins also can
+  // contain logic and perform e.g. database queries that fetch data to present.
+  supportsImplicitInputs: TRUE,
 )]
 final class BlockComponent extends ComponentSourceBase implements ContainerFactoryPluginInterface {
 
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
index 460814d25f..b69993bb78 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
@@ -21,7 +21,8 @@ use Drupal\experience_builder\Entity\JavaScriptComponent;
  */
 #[ComponentSource(
   id: self::SOURCE_PLUGIN_ID,
-  label: new TranslatableMarkup('Code Components')
+  label: new TranslatableMarkup('Code Components'),
+  supportsImplicitInputs: FALSE,
 )]
 final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase {
 
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index 51ffbb6bf1..5768e35b69 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -28,7 +28,8 @@ use Symfony\Component\Filesystem\Path;
  */
 #[ComponentSource(
   id: self::SOURCE_PLUGIN_ID,
-  label: new TranslatableMarkup('Single-Directory Components')
+  label: new TranslatableMarkup('Single-Directory Components'),
+  supportsImplicitInputs: FALSE,
 )]
 final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxComponentSourceBase implements UrlRewriteInterface {
 
-- 
GitLab


From 078d28cfee361ab00a37767b3d116a4938fc77bb Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Thu, 20 Feb 2025 19:46:18 +0100
Subject: [PATCH 18/30] Add missing metadata: each `Component` needs to know
 its provider: a theme name, module name, or NULL if neither.

---
 config/schema/experience_builder.schema.yml   |  9 +++++++
 experience_builder.module                     |  1 +
 src/Entity/Component.php                      | 26 ++++++++++++++++---
 .../ComponentSource/JsComponent.php           |  1 +
 .../SingleDirectoryComponent.php              |  1 +
 .../src/Functional/PropSourceEndpointTest.php |  1 +
 tests/src/Kernel/Config/ComponentTest.php     |  5 +++-
 .../Entity/JavascriptComponentStorageTest.php |  1 +
 8 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/config/schema/experience_builder.schema.yml b/config/schema/experience_builder.schema.yml
index b3c76f7716..e3352d5bc9 100644
--- a/config/schema/experience_builder.schema.yml
+++ b/config/schema/experience_builder.schema.yml
@@ -28,6 +28,15 @@ experience_builder.component.*:
         Length:
           # @see \Drupal\Core\Config\Entity\ConfigEntityStorage::MAX_ID_LENGTH
           max: 166
+    provider:
+      type: string
+      label: 'Name of the module or theme providing this component, or null if provided via something else'
+      nullable: true
+      constraints:
+        NotBlank:
+          allowNull: true
+        ExtensionName: []
+        Callback: ['\Drupal\experience_builder\Entity\Component', providerExists]
     source:
       type: string
       label: 'Source plugin'
diff --git a/experience_builder.module b/experience_builder.module
index ca9833a8d1..4e5efc170f 100644
--- a/experience_builder.module
+++ b/experience_builder.module
@@ -265,6 +265,7 @@ function experience_builder_block_alter(array &$definitions): void {
         'label' => (string) new TranslatableMarkup('@label block', ['@label' => $definition['admin_label']]),
         'category' => (string) $definition['category'],
         'source' => BlockComponent::SOURCE_PLUGIN_ID,
+        'provider' => $definition['provider'],
         'settings' => [
           'plugin_id' => $id,
           // We are using strict config schema validation, so we need to provide valid default settings for each block.
diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index aae0c016b0..ad9d274136 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -5,6 +5,8 @@ declare(strict_types=1);
 namespace Drupal\experience_builder\Entity;
 
 use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
 use Drupal\Core\Render\Markup;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
@@ -46,6 +48,7 @@ use Drupal\experience_builder\ComponentSource\ComponentSourceManager;
  *      "label",
  *      "id",
  *      "source",
+ *      "provider",
  *      "category",
  *      "settings",
  *    },
@@ -75,6 +78,16 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
    */
   protected string $source;
 
+  /**
+   * The provider of this component: a valid module or theme name, or NULL.
+   *
+   * NULL must be used to signal it's not provided by an extension. This is used
+   * for "code components" for example — which are provided by entities.
+   *
+   * @see \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\JsComponent
+   */
+  protected ?string $provider;
+
   /**
    * The human-readable category of the component.
    */
@@ -168,10 +181,17 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
   }
 
   /**
-   * {@inheritdoc}
+   * Works around the `ExtensionExists` constraint requiring a fixed type.
+   *
+   * @see \Drupal\Core\Extension\Plugin\Validation\Constraint\ExtensionExistsConstraintValidator
+   * @see https://www.drupal.org/node/3353397
    */
-  protected function providerExists(string $provider): bool {
-    return $this->moduleHandler()->moduleExists($provider) || $this->themeHandler()->themeExists($provider);
+  public static function providerExists(?string $provider): bool {
+    if (is_null($provider)) {
+      return TRUE;
+    }
+    $container = \Drupal::getContainer();
+    return $container->get(ModuleHandlerInterface::class)->moduleExists($provider) || $container->get(ThemeHandlerInterface::class)->themeExists($provider);
   }
 
   /**
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
index b69993bb78..2d631e001e 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
@@ -148,6 +148,7 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase
       'id' => self::SOURCE_PLUGIN_ID . '.' . $js_component->id(),
       'label' => $js_component->label(),
       'category' => '@todo',
+      'provider' => NULL,
       'source' => self::SOURCE_PLUGIN_ID,
       'settings' => [
         // @todo rename plugin_id in https://www.drupal.org/project/experience_builder/issues/3502982
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index 5768e35b69..86fe41d206 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -221,6 +221,7 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon
       'label' => $component_plugin->getPluginDefinition()['name'] ?? $component_plugin->getPluginId(),
       'category' => $component_plugin->getPluginDefinition()['category'],
       'source' => self::SOURCE_PLUGIN_ID,
+      'provider' => $component_plugin->getPluginDefinition()['provider'],
       'settings' => [
         'plugin_id' => $component_plugin->getPluginId(),
         'prop_field_definitions' => $props,
diff --git a/tests/src/Functional/PropSourceEndpointTest.php b/tests/src/Functional/PropSourceEndpointTest.php
index 49bde26665..e337ca24b2 100644
--- a/tests/src/Functional/PropSourceEndpointTest.php
+++ b/tests/src/Functional/PropSourceEndpointTest.php
@@ -122,6 +122,7 @@ class PropSourceEndpointTest extends FunctionalTestBase {
       $this->assertArrayHasKey('name', $component);
       $this->assertArrayHasKey('category', $component);
       $this->assertArrayHasKey('source', $component);
+      $this->assertArrayHasKey('provider', $component);
       $this->assertArrayHasKey('default_markup', $component);
       $this->assertArrayHasKey('css', $component);
       $this->assertArrayHasKey('js_header', $component);
diff --git a/tests/src/Kernel/Config/ComponentTest.php b/tests/src/Kernel/Config/ComponentTest.php
index 76b468a3db..bf241b6bc2 100644
--- a/tests/src/Kernel/Config/ComponentTest.php
+++ b/tests/src/Kernel/Config/ComponentTest.php
@@ -103,6 +103,7 @@ class ComponentTest extends KernelTestBase {
       'sdc' => [
         'component_config_entity_id' => 'sdc.sdc_test.my-cta',
         'source' => SingleDirectoryComponent::SOURCE_PLUGIN_ID,
+        'provider' => 'sdc_test',
         'source_internal_id' => 'sdc_test:my-cta',
         'expected_config_dependencies' => [
           'module' => [
@@ -116,6 +117,7 @@ class ComponentTest extends KernelTestBase {
       'js' => [
         'component_config_entity_id' => 'js.my-cta',
         'source' => JsComponent::SOURCE_PLUGIN_ID,
+        'provider' => NULL,
         'source_internal_id' => 'my-cta',
         'expected_config_dependencies' => [
           'config' => [
@@ -133,7 +135,7 @@ class ComponentTest extends KernelTestBase {
   /**
    * @dataProvider providerComponentCreation
    */
-  public function testComponentCreation(string $component_config_entity_id, string $source, string $source_internal_id, array $expected_config_dependencies): void {
+  public function testComponentCreation(string $component_config_entity_id, string $source, ?string $provider, string $source_internal_id, array $expected_config_dependencies): void {
     if ($source === JsComponent::SOURCE_PLUGIN_ID) {
       $this->assertEmpty(JavaScriptComponent::loadMultiple());
 
@@ -168,6 +170,7 @@ class ComponentTest extends KernelTestBase {
       'label' => self::LABEL,
       'category' => self::LABEL,
       'source' => $source,
+      'provider' => $provider,
       'settings' => [
         'plugin_id' => $source_internal_id,
         'prop_field_definitions' => [
diff --git a/tests/src/Kernel/Entity/JavascriptComponentStorageTest.php b/tests/src/Kernel/Entity/JavascriptComponentStorageTest.php
index 61600aff16..85b09283d0 100644
--- a/tests/src/Kernel/Entity/JavascriptComponentStorageTest.php
+++ b/tests/src/Kernel/Entity/JavascriptComponentStorageTest.php
@@ -139,6 +139,7 @@ final class JavascriptComponentStorageTest extends KernelTestBase {
 
     $component = Component::load($component_id);
     self::assertInstanceOf(ComponentInterface::class, $component);
+    self::assertNull($component->get('provider'));
     self::assertEquals(['title'], \array_keys($component->getSettings()['prop_field_definitions']));
 
     // Now update the js component and confirm we update the matching component.
-- 
GitLab


From d844a34b8120ceb178f9829f3d90c4629e1021a0 Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Thu, 20 Feb 2025 20:17:52 +0100
Subject: [PATCH 19/30] Introduce
 `XbHttpApiEligibleConfigEntityInterface::refineListQuery()` and use it to
 dynamically omit `Component`s provided by installed-but-not-default themes.

---
 src/Controller/ApiConfigControllers.php         |  8 +-------
 src/Entity/AssetLibrary.php                     |  8 ++++++++
 src/Entity/Component.php                        | 17 +++++++++++++++++
 src/Entity/JavaScriptComponent.php              |  8 ++++++++
 src/Entity/Pattern.php                          |  8 ++++++++
 .../XbHttpApiEligibleConfigEntityInterface.php  | 11 +++++++++++
 6 files changed, 53 insertions(+), 7 deletions(-)

diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php
index c3d618ac8f..c64ae7a5c0 100644
--- a/src/Controller/ApiConfigControllers.php
+++ b/src/Controller/ApiConfigControllers.php
@@ -84,6 +84,7 @@ final class ApiConfigControllers extends ApiControllerBase {
     if (!$xb_config_entity_type->get('xb_visible_when_disabled')) {
       $query->condition('status', TRUE);
     }
+    $xb_config_entity_type->getClass()::refineListQuery($query);
     /** @var array<\Drupal\experience_builder\Entity\XbHttpApiEligibleConfigEntityInterface> $config_entities */
     $config_entities = $storage->loadMultiple($query->execute());
 
@@ -95,13 +96,6 @@ final class ApiConfigControllers extends ApiControllerBase {
     $normalizations_cacheability = new CacheableMetadata();
     foreach ($config_entities as $key => &$entity) {
       $representation = $this->normalize($entity);
-      // If component is provided by non-default theme it should be excluded
-      // from the endpoint output.
-      // @see \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\SingleDirectoryComponent::getLibrary()
-      // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374
-      if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryEnum::None) {
-        continue;
-      }
       $normalizations[$key] = $representation->values;
       $normalizations_cacheability->addCacheableDependency($representation);
     }
diff --git a/src/Entity/AssetLibrary.php b/src/Entity/AssetLibrary.php
index 7cf7104c31..155fb025b1 100644
--- a/src/Entity/AssetLibrary.php
+++ b/src/Entity/AssetLibrary.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Drupal\experience_builder\Entity;
 
 use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\experience_builder\ClientSideRepresentation;
 
 /**
@@ -73,4 +74,11 @@ final class AssetLibrary extends ConfigEntityBase implements XbHttpApiEligibleCo
     return $data;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function refineListQuery(QueryInterface &$query): void {
+    // Nothing to do.
+  }
+
 }
diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index ad9d274136..c018a48838 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Drupal\experience_builder\Entity;
 
 use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
@@ -247,6 +248,22 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
     throw new \LogicException('Not supported: read-only for the client side, mutable only on the server side.');
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function refineListQuery(QueryInterface &$query): void {
+    $container = \Drupal::getContainer();
+    $theme_handler = $container->get(ThemeHandlerInterface::class);
+    $installed_themes = array_keys($theme_handler->listInfo());
+    $default_theme = $theme_handler->getDefault();
+    // Omit Components provided by installed-but-not-default themes. This keeps
+    // all other Components:
+    // - module-provided ones
+    // - default theme-provided
+    // - provided by something else than an extension, such as an entity.
+    $query->condition('provider', array_diff($installed_themes, [$default_theme]), 'NOT IN');
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/src/Entity/JavaScriptComponent.php b/src/Entity/JavaScriptComponent.php
index b23e6ad96d..1c3730ae2c 100644
--- a/src/Entity/JavaScriptComponent.php
+++ b/src/Entity/JavaScriptComponent.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Drupal\experience_builder\Entity;
 
 use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\experience_builder\ClientSideRepresentation;
 
 /**
@@ -139,6 +140,13 @@ final class JavaScriptComponent extends ConfigEntityBase implements XbHttpApiEli
     ];
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function refineListQuery(QueryInterface &$query): void {
+    // Nothing to do.
+  }
+
   /**
    * Code components are not Twig-defined but still aim to match SDC closely.
    *
diff --git a/src/Entity/Pattern.php b/src/Entity/Pattern.php
index ddb23dad5f..5d3c6d3d77 100644
--- a/src/Entity/Pattern.php
+++ b/src/Entity/Pattern.php
@@ -7,6 +7,7 @@ namespace Drupal\experience_builder\Entity;
 use Drupal\Component\Utility\Random;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\experience_builder\ClientSideRepresentation;
 use Drupal\experience_builder\Controller\ApiConfigControllers;
 use Drupal\experience_builder\Controller\ClientServerConversionTrait;
@@ -163,4 +164,11 @@ final class Pattern extends ConfigEntityBase implements XbHttpApiEligibleConfigE
     ] + $other_values;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function refineListQuery(QueryInterface &$query): void {
+    // Nothing to do.
+  }
+
 }
diff --git a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php
index 4a9710cbbc..3ed8183af8 100644
--- a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php
+++ b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Drupal\experience_builder\Entity;
 
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\experience_builder\ClientSideRepresentation;
 
 /**
@@ -33,4 +34,14 @@ interface XbHttpApiEligibleConfigEntityInterface extends ConfigEntityInterface {
    */
   public static function denormalizeFromClientSide(array $data): array;
 
+  /**
+   * Allows the config entity query that generates the listing to be refined.
+   *
+   * @param \Drupal\Core\Entity\Query\QueryInterface $query
+   *   The config entity query to refine, passed by reference.
+   *
+   * @return void
+   */
+  public static function refineListQuery(QueryInterface &$query): void;
+
 }
-- 
GitLab


From 830cfc100dbf610e29bcdb10b8801a07cc12341e Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Fri, 21 Feb 2025 10:44:49 +0100
Subject: [PATCH 20/30] Update test expectations for `ComponentValidationTest`
 to account for the new `provider` property.

---
 tests/src/Kernel/Config/ComponentValidationTest.php | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/tests/src/Kernel/Config/ComponentValidationTest.php b/tests/src/Kernel/Config/ComponentValidationTest.php
index c802908e04..08837eeed0 100644
--- a/tests/src/Kernel/Config/ComponentValidationTest.php
+++ b/tests/src/Kernel/Config/ComponentValidationTest.php
@@ -49,6 +49,15 @@ class ComponentValidationTest extends ConfigEntityValidationTestBase {
     ],
   ];
 
+  /**
+   * {@inheritdoc}
+   *
+   * @phpstan-ignore property.defaultValue
+   */
+  protected static array $propertiesWithOptionalValues = [
+    'provider',
+  ];
+
   /**
    * {@inheritdoc}
    */
-- 
GitLab


From ef0b3121ce5443db76c482c99b13a6294a5dba73 Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Fri, 21 Feb 2025 11:09:30 +0100
Subject: [PATCH 21/30] Move the library computing logic from the SDC source
 into a private `::computeUiLibrary()` method used only during `Component`
 normalization. Hence it is now consistent across all Component Sources.

---
 .../ComponentSourceInterface.php              |  8 ----
 src/Entity/Component.php                      | 48 ++++++++++++++++++-
 .../ComponentSource/BlockComponent.php        |  8 ----
 .../ComponentSource/JsComponent.php           |  8 ----
 .../SingleDirectoryComponent.php              | 21 --------
 .../src/Functional/PropSourceEndpointTest.php |  2 +-
 6 files changed, 48 insertions(+), 47 deletions(-)

diff --git a/src/ComponentSource/ComponentSourceInterface.php b/src/ComponentSource/ComponentSourceInterface.php
index d740e57fbb..7e72bafdf9 100644
--- a/src/ComponentSource/ComponentSourceInterface.php
+++ b/src/ComponentSource/ComponentSourceInterface.php
@@ -14,7 +14,6 @@ use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Drupal\Core\Plugin\PluginFormInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
-use Drupal\experience_builder\Controller\LibraryEnum;
 use Drupal\experience_builder\Entity\Component;
 use Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem;
 use Symfony\Component\Validator\ConstraintViolationListInterface;
@@ -206,11 +205,4 @@ interface ComponentSourceInterface extends PluginInspectionInterface, Derivative
    */
   public function checkRequirements(): void;
 
-  /**
-   * Returns Library enum.
-   *
-   * @return \Drupal\experience_builder\Controller\LibraryEnum
-   */
-  public function getLibrary(): LibraryEnum;
-
 }
diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index c018a48838..572db7ce8f 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -14,6 +14,7 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\experience_builder\ClientSideRepresentation;
 use Drupal\experience_builder\ComponentSource\ComponentSourceInterface;
 use Drupal\experience_builder\ComponentSource\ComponentSourceManager;
+use Drupal\experience_builder\Controller\LibraryEnum;
 
 /**
  * @todo Update these docs in https://drupal.org/i/3454519 to reflect changes.
@@ -231,7 +232,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
       values: $info + [
         'id' => $this->id(),
         'name' => (string) $this->label(),
-        'library' => $this->getComponentSource()->getLibrary()->value,
+        'library' => $this->computeUiLibrary()->value,
         'category' => (string) $this->getCategory(),
         'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'],
       ],
@@ -239,6 +240,51 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
     )->addCacheableDependency($this);
   }
 
+  /**
+   * Uses heuristics to compute the appropriate "library" in the XB UI.
+   *
+   * Each Component appears in a well-defined "library" in the XB UI. This is a
+   * set of heuristics with a particular decision tree.
+   *
+   * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997505
+   */
+  private function computeUiLibrary(): LibraryEnum {
+    $config = \Drupal::configFactory()->loadMultiple(['core.extension', 'system.theme']);
+    $installed_modules = [
+      'core',
+      ...array_keys($config['core.extension']->get('module'))
+    ];
+    // @see \Drupal\Core\Extension\ThemeHandler::getDefault()
+    $default_theme = $config['system.theme']->get('default');
+
+    // 1. Is the component dynamic (consumes implicit inputs/context or has
+    // logic)?
+    if ($this->getComponentSource()->getPluginDefinition()['supportsImplicitInputs']) {
+      return LibraryEnum::DynamicComponents;
+    }
+
+    // 2. Is the component provided by a module?
+    if (in_array($this->provider, $installed_modules, TRUE)) {
+      return $this->provider === 'experience_builder'
+        // 2.B I the providing module XB?
+        ? LibraryEnum::Elements
+        : LibraryEnum::ExtensionComponents;
+    }
+
+    // 3. Is the component provided by the default theme (or its base theme)?
+    if ($this->provider === $default_theme) {
+      return LibraryEnum::PrimaryComponents;
+    }
+
+    // 4. Is the component provided by neither a theme nor a module?
+    if ($this->provider === NULL) {
+      return LibraryEnum::PrimaryComponents;
+    }
+
+    return LibraryEnum::None;
+  }
+
+
   /**
    * {@inheritdoc}
    *
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
index 64aa781c74..a9ae5fadf8 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
@@ -22,7 +22,6 @@ use Drupal\Core\TypedData\Plugin\DataType\BooleanData;
 use Drupal\Core\TypedData\TypedDataInterface;
 use Drupal\experience_builder\Attribute\ComponentSource;
 use Drupal\experience_builder\ComponentSource\ComponentSourceBase;
-use Drupal\experience_builder\Controller\LibraryEnum;
 use Drupal\experience_builder\Entity\Component;
 use Drupal\experience_builder\Entity\Component as ComponentEntity;
 use Drupal\experience_builder\MissingComponentInputsException;
@@ -362,11 +361,4 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto
     // @todo Move logic from experience_builder_block_alter here in https://www.drupal.org/project/experience_builder/issues/3491032
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getLibrary(): LibraryEnum {
-    return LibraryEnum::DynamicComponents;
-  }
-
 }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
index 2d631e001e..19c12514ee 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
@@ -11,7 +11,6 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\experience_builder\Attribute\ComponentSource;
 use Drupal\experience_builder\ComponentDoesNotMeetRequirementsException;
 use Drupal\experience_builder\ComponentMetadataRequirementsChecker;
-use Drupal\experience_builder\Controller\LibraryEnum;
 use Drupal\experience_builder\Entity\Component as ComponentEntity;
 use Drupal\experience_builder\Entity\ComponentInterface;
 use Drupal\experience_builder\Entity\JavaScriptComponent;
@@ -229,11 +228,4 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase
     );
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getLibrary(): LibraryEnum {
-    return LibraryEnum::PrimaryComponents;
-  }
-
 }
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
index 86fe41d206..de628fd338 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php
@@ -16,7 +16,6 @@ use Drupal\Core\Theme\ComponentPluginManager;
 use Drupal\Core\Theme\ExtensionType;
 use Drupal\experience_builder\Attribute\ComponentSource;
 use Drupal\experience_builder\ComponentSource\UrlRewriteInterface;
-use Drupal\experience_builder\Controller\LibraryEnum;
 use Drupal\experience_builder\Entity\Component as ComponentEntity;
 use Drupal\experience_builder\Plugin\ComponentPluginManager as XbComponentPluginManager;
 use Drupal\experience_builder\ShapeMatcher\FieldForComponentSuggester;
@@ -313,24 +312,4 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon
     return \base_path() . $path;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getLibrary(): LibraryEnum {
-    $base_id = $this->getComponentPlugin()->getBaseId();
-
-    if ($this->moduleHandler->moduleExists($base_id)) {
-      return $base_id === 'experience_builder' ? LibraryEnum::Elements : LibraryEnum::ExtensionComponents;
-    }
-
-    if ($this->themeHandler->getDefault() === $base_id) {
-      return LibraryEnum::PrimaryComponents;
-    }
-
-    // If component is provided by non-default theme it should not be assigned
-    // 'primary_components' library value.
-    // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374
-    return LibraryEnum::None;
-  }
-
 }
diff --git a/tests/src/Functional/PropSourceEndpointTest.php b/tests/src/Functional/PropSourceEndpointTest.php
index e337ca24b2..3fa0727d9b 100644
--- a/tests/src/Functional/PropSourceEndpointTest.php
+++ b/tests/src/Functional/PropSourceEndpointTest.php
@@ -122,7 +122,7 @@ class PropSourceEndpointTest extends FunctionalTestBase {
       $this->assertArrayHasKey('name', $component);
       $this->assertArrayHasKey('category', $component);
       $this->assertArrayHasKey('source', $component);
-      $this->assertArrayHasKey('provider', $component);
+      $this->assertArrayHasKey('library', $component, $id);
       $this->assertArrayHasKey('default_markup', $component);
       $this->assertArrayHasKey('css', $component);
       $this->assertArrayHasKey('js_header', $component);
-- 
GitLab


From 357e7979e0c42ea5fd1f3f8a71ef510479ba0435 Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Fri, 21 Feb 2025 11:55:30 +0100
Subject: [PATCH 22/30] `phpcs` + `phpstan`

---
 src/Entity/Component.php                            | 3 +--
 tests/src/Kernel/Config/ComponentValidationTest.php | 2 --
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index 572db7ce8f..ba3603940f 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -252,7 +252,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
     $config = \Drupal::configFactory()->loadMultiple(['core.extension', 'system.theme']);
     $installed_modules = [
       'core',
-      ...array_keys($config['core.extension']->get('module'))
+      ...array_keys($config['core.extension']->get('module')),
     ];
     // @see \Drupal\Core\Extension\ThemeHandler::getDefault()
     $default_theme = $config['system.theme']->get('default');
@@ -284,7 +284,6 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
     return LibraryEnum::None;
   }
 
-
   /**
    * {@inheritdoc}
    *
diff --git a/tests/src/Kernel/Config/ComponentValidationTest.php b/tests/src/Kernel/Config/ComponentValidationTest.php
index 08837eeed0..7680c983a8 100644
--- a/tests/src/Kernel/Config/ComponentValidationTest.php
+++ b/tests/src/Kernel/Config/ComponentValidationTest.php
@@ -51,8 +51,6 @@ class ComponentValidationTest extends ConfigEntityValidationTestBase {
 
   /**
    * {@inheritdoc}
-   *
-   * @phpstan-ignore property.defaultValue
    */
   protected static array $propertiesWithOptionalValues = [
     'provider',
-- 
GitLab


From eb1aa57cece2b40b758cf2c9ee6a675eb559246f Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Fri, 21 Feb 2025 11:23:36 +0100
Subject: [PATCH 23/30] `NOT IN` apparently excludes `NULL` values, so change
 to using `::orConditionGroup()`.

---
 src/Entity/Component.php | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index ba3603940f..5f86e1fc71 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -306,7 +306,10 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
     // - module-provided ones
     // - default theme-provided
     // - provided by something else than an extension, such as an entity.
-    $query->condition('provider', array_diff($installed_themes, [$default_theme]), 'NOT IN');
+    $or_group = $query->orConditionGroup()
+      ->condition('provider', operator: 'NOT IN', value: array_diff($installed_themes, [$default_theme]))
+      ->condition('provider', operator: 'IS NULL');
+    $query->condition($or_group);
   }
 
   /**
-- 
GitLab


From c01e7be200dde495bc8c51933c0f46860919e6e8 Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Fri, 21 Feb 2025 10:54:31 +0100
Subject: [PATCH 24/30] Query cacheability must be refined, to match the config
 entity query being refined.

---
 src/Controller/ApiConfigControllers.php              |  6 +++---
 src/Entity/AssetLibrary.php                          |  3 ++-
 src/Entity/Component.php                             | 12 +++++++++++-
 src/Entity/JavaScriptComponent.php                   |  3 ++-
 src/Entity/Pattern.php                               |  3 ++-
 .../XbHttpApiEligibleConfigEntityInterface.php       |  6 +++++-
 tests/src/Functional/PropSourceEndpointTest.php      |  2 ++
 7 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php
index c64ae7a5c0..62d02fc2ce 100644
--- a/src/Controller/ApiConfigControllers.php
+++ b/src/Controller/ApiConfigControllers.php
@@ -84,13 +84,13 @@ final class ApiConfigControllers extends ApiControllerBase {
     if (!$xb_config_entity_type->get('xb_visible_when_disabled')) {
       $query->condition('status', TRUE);
     }
-    $xb_config_entity_type->getClass()::refineListQuery($query);
-    /** @var array<\Drupal\experience_builder\Entity\XbHttpApiEligibleConfigEntityInterface> $config_entities */
-    $config_entities = $storage->loadMultiple($query->execute());
 
     $query_cacheability = (new CacheableMetadata())
       ->addCacheContexts($xb_config_entity_type->getListCacheContexts())
       ->addCacheTags($xb_config_entity_type->getListCacheTags());
+    $xb_config_entity_type->getClass()::refineListQuery($query, $query_cacheability);
+    /** @var array<\Drupal\experience_builder\Entity\XbHttpApiEligibleConfigEntityInterface> $config_entities */
+    $config_entities = $storage->loadMultiple($query->execute());
 
     $normalizations = [];
     $normalizations_cacheability = new CacheableMetadata();
diff --git a/src/Entity/AssetLibrary.php b/src/Entity/AssetLibrary.php
index 155fb025b1..b31e58e6d9 100644
--- a/src/Entity/AssetLibrary.php
+++ b/src/Entity/AssetLibrary.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\experience_builder\Entity;
 
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\experience_builder\ClientSideRepresentation;
@@ -77,7 +78,7 @@ final class AssetLibrary extends ConfigEntityBase implements XbHttpApiEligibleCo
   /**
    * {@inheritdoc}
    */
-  public static function refineListQuery(QueryInterface &$query): void {
+  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void {
     // Nothing to do.
   }
 
diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index 5f86e1fc71..4ec4183771 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\experience_builder\Entity;
 
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
@@ -296,11 +297,12 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
   /**
    * {@inheritdoc}
    */
-  public static function refineListQuery(QueryInterface &$query): void {
+  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void {
     $container = \Drupal::getContainer();
     $theme_handler = $container->get(ThemeHandlerInterface::class);
     $installed_themes = array_keys($theme_handler->listInfo());
     $default_theme = $theme_handler->getDefault();
+
     // Omit Components provided by installed-but-not-default themes. This keeps
     // all other Components:
     // - module-provided ones
@@ -310,6 +312,14 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
       ->condition('provider', operator: 'NOT IN', value: array_diff($installed_themes, [$default_theme]))
       ->condition('provider', operator: 'IS NULL');
     $query->condition($or_group);
+
+    // Reflect the conditions added to the query in the cacheability.
+    $cacheablity->addCacheTags([
+      // The set of installed themes is stored in the `core.extension` config.
+      'config:core.extension',
+      // The default theme is stored in the `system.theme` config.
+      'config:system.theme',
+    ]);
   }
 
   /**
diff --git a/src/Entity/JavaScriptComponent.php b/src/Entity/JavaScriptComponent.php
index 1c3730ae2c..036de9c7b4 100644
--- a/src/Entity/JavaScriptComponent.php
+++ b/src/Entity/JavaScriptComponent.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\experience_builder\Entity;
 
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\experience_builder\ClientSideRepresentation;
@@ -143,7 +144,7 @@ final class JavaScriptComponent extends ConfigEntityBase implements XbHttpApiEli
   /**
    * {@inheritdoc}
    */
-  public static function refineListQuery(QueryInterface &$query): void {
+  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void {
     // Nothing to do.
   }
 
diff --git a/src/Entity/Pattern.php b/src/Entity/Pattern.php
index 5d3c6d3d77..6e8ec55e69 100644
--- a/src/Entity/Pattern.php
+++ b/src/Entity/Pattern.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Drupal\experience_builder\Entity;
 
 use Drupal\Component\Utility\Random;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\Query\QueryInterface;
@@ -167,7 +168,7 @@ final class Pattern extends ConfigEntityBase implements XbHttpApiEligibleConfigE
   /**
    * {@inheritdoc}
    */
-  public static function refineListQuery(QueryInterface &$query): void {
+  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void {
     // Nothing to do.
   }
 
diff --git a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php
index 3ed8183af8..e0d5d37ee5 100644
--- a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php
+++ b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\experience_builder\Entity;
 
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
 use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\experience_builder\ClientSideRepresentation;
@@ -39,9 +40,12 @@ interface XbHttpApiEligibleConfigEntityInterface extends ConfigEntityInterface {
    *
    * @param \Drupal\Core\Entity\Query\QueryInterface $query
    *   The config entity query to refine, passed by reference.
+   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheablity
+   *   The cacheability of the given query, to be refined to match the
+   *   refinements made to the query.
    *
    * @return void
    */
-  public static function refineListQuery(QueryInterface &$query): void;
+  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void;
 
 }
diff --git a/tests/src/Functional/PropSourceEndpointTest.php b/tests/src/Functional/PropSourceEndpointTest.php
index 3fa0727d9b..dffb567664 100644
--- a/tests/src/Functional/PropSourceEndpointTest.php
+++ b/tests/src/Functional/PropSourceEndpointTest.php
@@ -58,12 +58,14 @@ class PropSourceEndpointTest extends FunctionalTestBase {
       'announcements_feed:feed',
       'comment_list',
       'config:component_list',
+      'config:core.extension',
       'config:search.settings',
       'config:system.menu.account',
       'config:system.menu.admin',
       'config:system.menu.footer',
       'config:system.menu.main',
       'config:system.site',
+      'config:system.theme',
       'config:views.view.comments_recent',
       'config:views.view.content_recent',
       'config:views.view.who_s_new',
-- 
GitLab


From 5bdbe220da2fed4bc60e5b4e22f6058388953de6 Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Fri, 21 Feb 2025 12:35:02 +0100
Subject: [PATCH 25/30] =?UTF-8?q?Spelling=20=F0=9F=99=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/Entity/AssetLibrary.php                           | 2 +-
 src/Entity/Component.php                              | 4 ++--
 src/Entity/JavaScriptComponent.php                    | 2 +-
 src/Entity/Pattern.php                                | 2 +-
 src/Entity/XbHttpApiEligibleConfigEntityInterface.php | 4 ++--
 5 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/Entity/AssetLibrary.php b/src/Entity/AssetLibrary.php
index b31e58e6d9..727266ced6 100644
--- a/src/Entity/AssetLibrary.php
+++ b/src/Entity/AssetLibrary.php
@@ -78,7 +78,7 @@ final class AssetLibrary extends ConfigEntityBase implements XbHttpApiEligibleCo
   /**
    * {@inheritdoc}
    */
-  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void {
+  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheability): void {
     // Nothing to do.
   }
 
diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index 4ec4183771..bd81f78286 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -297,7 +297,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
   /**
    * {@inheritdoc}
    */
-  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void {
+  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheability): void {
     $container = \Drupal::getContainer();
     $theme_handler = $container->get(ThemeHandlerInterface::class);
     $installed_themes = array_keys($theme_handler->listInfo());
@@ -314,7 +314,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
     $query->condition($or_group);
 
     // Reflect the conditions added to the query in the cacheability.
-    $cacheablity->addCacheTags([
+    $cacheability->addCacheTags([
       // The set of installed themes is stored in the `core.extension` config.
       'config:core.extension',
       // The default theme is stored in the `system.theme` config.
diff --git a/src/Entity/JavaScriptComponent.php b/src/Entity/JavaScriptComponent.php
index 036de9c7b4..898c6135a8 100644
--- a/src/Entity/JavaScriptComponent.php
+++ b/src/Entity/JavaScriptComponent.php
@@ -144,7 +144,7 @@ final class JavaScriptComponent extends ConfigEntityBase implements XbHttpApiEli
   /**
    * {@inheritdoc}
    */
-  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void {
+  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheability): void {
     // Nothing to do.
   }
 
diff --git a/src/Entity/Pattern.php b/src/Entity/Pattern.php
index 6e8ec55e69..c5afbd8d1d 100644
--- a/src/Entity/Pattern.php
+++ b/src/Entity/Pattern.php
@@ -168,7 +168,7 @@ final class Pattern extends ConfigEntityBase implements XbHttpApiEligibleConfigE
   /**
    * {@inheritdoc}
    */
-  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void {
+  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheability): void {
     // Nothing to do.
   }
 
diff --git a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php
index e0d5d37ee5..369433cd00 100644
--- a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php
+++ b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php
@@ -40,12 +40,12 @@ interface XbHttpApiEligibleConfigEntityInterface extends ConfigEntityInterface {
    *
    * @param \Drupal\Core\Entity\Query\QueryInterface $query
    *   The config entity query to refine, passed by reference.
-   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheablity
+   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability
    *   The cacheability of the given query, to be refined to match the
    *   refinements made to the query.
    *
    * @return void
    */
-  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void;
+  public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheability): void;
 
 }
-- 
GitLab


From 9fbb29cb60cdae802030908eb433e5b8635690b2 Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Fri, 21 Feb 2025 12:58:03 +0100
Subject: [PATCH 26/30] Add test coverage for changing the default theme.

---
 .../src/Functional/PropSourceEndpointTest.php | 28 +++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/tests/src/Functional/PropSourceEndpointTest.php b/tests/src/Functional/PropSourceEndpointTest.php
index dffb567664..b536f582b1 100644
--- a/tests/src/Functional/PropSourceEndpointTest.php
+++ b/tests/src/Functional/PropSourceEndpointTest.php
@@ -133,6 +133,10 @@ class PropSourceEndpointTest extends FunctionalTestBase {
     $this->assertStringStartsWith('<!-- xb-start-', $data['block.system_menu_block.main']['default_markup']);
     $this->assertStringContainsString('--><nav role="navigation"', $data['block.system_menu_block.main']['default_markup']);
 
+    // Stark has no SDCs.
+    $this->assertSame('stark', $this->config('system.theme')->get('default'));
+    $this->assertArrayNotHasKey('sdc.olivero.teaser', $data);
+
     $data = array_intersect_key(
       $data,
       [
@@ -166,6 +170,30 @@ class PropSourceEndpointTest extends FunctionalTestBase {
     self::assertEquals($extractValue, $data['sdc.sdc_test_all_props.all-props']['transforms']['test_integer']);
     self::assertEquals(['link' => []], $data['sdc.sdc_test_all_props.all-props']['transforms']['test_string_format_uri']);
     self::assertEquals(['mediaSelection' => [], 'mainProperty' => ['name' => 'target_id']], $data['sdc.sdc_test_all_props.all-props']['transforms']['test_object_drupal_image']);
+
+    // Olivero does have an SDC, and it's enabled, but it is omitted because the
+    // default theme is Stark.
+    $this->assertInstanceOf(Component::class, Component::load('sdc.olivero.teaser'));
+    $this->assertTrue(Component::load('sdc.olivero.teaser')->status());
+    $this->assertSame('olivero', Component::load('sdc.olivero.teaser')->get('provider'));
+
+    // Change the default theme from Stark to Olivero, and observe the impact on
+    // the list of Components returned.
+    \Drupal::configFactory()->getEditable('system.theme')->set('default', 'olivero')->save();
+    $this->rebuildAll();
+    $this->drupalGet('xb/api/config/component');
+    $data = Json::decode($page->getText());
+    $this->assertSession()->responseHeaderEquals('X-Drupal-Dynamic-Cache', 'MISS');
+    $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'UNCACHEABLE (request policy)');
+    // Olivero does have an SDC!
+    $this->assertSame('olivero', $this->config('system.theme')->get('default'));
+    $this->assertArrayHasKey('sdc.olivero.teaser', $data);
+    // Repeated request is again a Dynamic Page Cache hit.
+    $this->drupalGet('xb/api/config/component');
+    $this->assertSession()->responseHeaderEquals('X-Drupal-Dynamic-Cache', 'HIT');
+    $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'UNCACHEABLE (request policy)');
+
+
   }
 
   /**
-- 
GitLab


From 00d3f751f036cc45aa96c010368d31f9aecc16c2 Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <56971-f.mazeikis@users.noreply.drupalcode.org>
Date: Fri, 21 Feb 2025 12:15:45 +0000
Subject: [PATCH 27/30] Apply 3 suggestion(s) to 3 file(s)

Co-authored-by: Wim Leers <44946-wimleers@users.noreply.drupalcode.org>
---
 src/Controller/LibraryEnum.php                  | 1 -
 src/Entity/Component.php                        | 2 +-
 tests/src/Functional/PropSourceEndpointTest.php | 2 --
 3 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/Controller/LibraryEnum.php b/src/Controller/LibraryEnum.php
index feaabc0675..b943af1691 100644
--- a/src/Controller/LibraryEnum.php
+++ b/src/Controller/LibraryEnum.php
@@ -14,5 +14,4 @@ enum LibraryEnum: string {
   case DynamicComponents = 'dynamic_components';
   case PrimaryComponents = 'primary_components';
   case None = '';
-
 }
diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index bd81f78286..9ab2d4f49f 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -282,7 +282,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
       return LibraryEnum::PrimaryComponents;
     }
 
-    return LibraryEnum::None;
+    throw new \LogicException('A Component is being normalized that belongs in no XB UI library.');
   }
 
   /**
diff --git a/tests/src/Functional/PropSourceEndpointTest.php b/tests/src/Functional/PropSourceEndpointTest.php
index b536f582b1..1e5b07dcf0 100644
--- a/tests/src/Functional/PropSourceEndpointTest.php
+++ b/tests/src/Functional/PropSourceEndpointTest.php
@@ -192,8 +192,6 @@ class PropSourceEndpointTest extends FunctionalTestBase {
     $this->drupalGet('xb/api/config/component');
     $this->assertSession()->responseHeaderEquals('X-Drupal-Dynamic-Cache', 'HIT');
     $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'UNCACHEABLE (request policy)');
-
-
   }
 
   /**
-- 
GitLab


From ab9af541996a61fbe74aed0d55aba8ad7b63ccdc Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <felix.mazeikis@acquia.com>
Date: Fri, 21 Feb 2025 13:11:38 +0000
Subject: [PATCH 28/30] Move LibraryEnum and remove None case

---
 src/Entity/Component.php                   | 1 -
 src/{Controller => Entity}/LibraryEnum.php | 3 +--
 2 files changed, 1 insertion(+), 3 deletions(-)
 rename src/{Controller => Entity}/LibraryEnum.php (86%)

diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index 9ab2d4f49f..1aa4a29478 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -15,7 +15,6 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\experience_builder\ClientSideRepresentation;
 use Drupal\experience_builder\ComponentSource\ComponentSourceInterface;
 use Drupal\experience_builder\ComponentSource\ComponentSourceManager;
-use Drupal\experience_builder\Controller\LibraryEnum;
 
 /**
  * @todo Update these docs in https://drupal.org/i/3454519 to reflect changes.
diff --git a/src/Controller/LibraryEnum.php b/src/Entity/LibraryEnum.php
similarity index 86%
rename from src/Controller/LibraryEnum.php
rename to src/Entity/LibraryEnum.php
index b943af1691..206d7e97c8 100644
--- a/src/Controller/LibraryEnum.php
+++ b/src/Entity/LibraryEnum.php
@@ -2,7 +2,7 @@
 
 declare(strict_types=1);
 
-namespace Drupal\experience_builder\Controller;
+namespace Drupal\experience_builder\Entity;
 
 /**
  * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15988043
@@ -13,5 +13,4 @@ enum LibraryEnum: string {
   case ExtensionComponents = 'extension_components';
   case DynamicComponents = 'dynamic_components';
   case PrimaryComponents = 'primary_components';
-  case None = '';
 }
-- 
GitLab


From d5a78b9ebace315dbc835464de1eca64920f7dcc Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <56971-f.mazeikis@users.noreply.drupalcode.org>
Date: Fri, 21 Feb 2025 13:17:47 +0000
Subject: [PATCH 29/30] Apply 1 suggestion(s) to 1 file(s)

Co-authored-by: Wim Leers <44946-wimleers@users.noreply.drupalcode.org>
---
 src/Entity/Component.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Entity/Component.php b/src/Entity/Component.php
index 1aa4a29478..62fcf3facf 100644
--- a/src/Entity/Component.php
+++ b/src/Entity/Component.php
@@ -266,7 +266,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb
     // 2. Is the component provided by a module?
     if (in_array($this->provider, $installed_modules, TRUE)) {
       return $this->provider === 'experience_builder'
-        // 2.B I the providing module XB?
+        // 2.B Is the providing module XB?
         ? LibraryEnum::Elements
         : LibraryEnum::ExtensionComponents;
     }
-- 
GitLab


From 2da72b7ac89e829ee9d5357c4e4651ca41387958 Mon Sep 17 00:00:00 2001
From: Feliksas Mazeikis <56971-f.mazeikis@users.noreply.drupalcode.org>
Date: Fri, 21 Feb 2025 14:20:20 +0000
Subject: [PATCH 30/30] Apply 1 suggestion(s) to 1 file(s)

Co-authored-by: Wim Leers <44946-wimleers@users.noreply.drupalcode.org>
---
 src/Entity/LibraryEnum.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Entity/LibraryEnum.php b/src/Entity/LibraryEnum.php
index 206d7e97c8..2d05f9ce4d 100644
--- a/src/Entity/LibraryEnum.php
+++ b/src/Entity/LibraryEnum.php
@@ -5,8 +5,8 @@ declare(strict_types=1);
 namespace Drupal\experience_builder\Entity;
 
 /**
- * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15988043
- * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15989118
+ * @internal
+ * @see \Drupal\experience_builder\Entity\Component::computeUiLibrary()
  */
 enum LibraryEnum: string {
   case Elements = 'elements';
-- 
GitLab