Update the React client preview view

Created on 21 May 2025, 17 days ago

Overview

Postponed on πŸ“Œ Personalization component source Active
Postponed on πŸ“Œ Personalization component source Active

Once we have a Personalization component source, we need to update the React UI preview view.

Proposed resolution

  • Edit the React UI preview component (and the overlay), as it must:
    • render only those components without wrapper,
    • render those components on the active personalization wrapper
    • render the "chips" for the active personalization wrappers
    • render the "chips" menu for the personalization wrapper operations (TBD)
  • Implement/Update integration tests (cypress? playwright?) for this UI change.

User interface changes

The Layers preview shows only the components that a user that matches the active personalization segment would see.
The wrapper has the chip with the necessary operations (TBD)

πŸ“Œ Task
Status

Postponed

Version

0.0

Component

Personalization

Created by

πŸ‡ͺπŸ‡ΈSpain penyaskito Seville πŸ’ƒ, Spain πŸ‡ͺπŸ‡Έ, UTC+2 πŸ‡ͺπŸ‡Ί

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @penyaskito
  • πŸ‡¬πŸ‡§United Kingdom jessebaker

    When building/designing this "chip" that shows the variant wrapper, consideration must be made for how we intend to show/handle "selecting components that are stacked one inside the other and take up the same space"

    It might be possible to address both at the same time if we can get designs that incorporate both things.

    As an example of a competitor's solution to the "components taking up the same space" here is a screenshot of Webflow showing the parent's of a selected component:

    Site Studio also has a solution that's quite neat (but I think could be improved upon). I can try and find a screenshot of that if it's helpful.

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    @jessebaker: how do you imagine the client will:

    • know to render such a chip?
    • know what label to use in the chip?

    The current thinking by @penyaskito, @effulgentsia and I (per meeting yesterday):

    • @penyaskito's proposal can be summarized as this:
    • Then adding new variants would add more siblings to uuid-here-personalization-wrapper-yo/variants.
    • To inform the client side about the fact that actually only one of the variants should be visible at the same time in both the preview and in the layers panel, we could allow nodeType: slot to contain a mutuallyExclusiveChildren: true flag.

      (Keeping the same set of nodeTypes but adding more metadata was proposed by @effulgentsia.)

      We'd set that on uuid-here-personalization-wrapper-yo/variants. It'd indicate that only one of its children is visible at a time.

      That same flag could then be used in future functionality where different subtrees should be visible in different circumstances, e.g. for Framer-like responsive design (which allows different component trees per breakpoint β€” not saying I think this is a good idea at all, just that it's something actually real-world-like).

    • We'd then also need a flag on nodeType: component to either:
      1. let the client side know what the segments are for each child to be visible (can happen entirely client-side upon selecting which segment the XB preview should preview πŸ‘ β€” at most requiring a round trip for updating the preview, but it's possible for the server to always provide that information and letting the client swap that in!)

        i.e.

        …
                                  // πŸ’‘The first variant, by default named "default".
                                  "nodeType": "component",
                                  "id": "uuid-here-personalization-variant-default",
                                  "type": "personalization.variant",
                                   // πŸ†• declarative choice
                                  "segments": ["belgium", "uk", "spain"],
                                  "slots": [
                                    {
                                      "nodeType": "slot",
                                      "id": "uuid-here-personalization-variant-default/variant",
                                      "components": [
                                        // πŸ’‘this is the original!
                                        {
                                          "nodeType": "component",
                                          "id": "baf231e8-b214-4e3e-93d3-5d3f03a1eae9",
                                          "type": "sdc.experience_builder.druplicon",
                                          "slots": []
                                        }
                                      ]
                                    }
                                  ]
        …
        
      2. let the client side know which of those children is currently active (requires talking to the server first πŸ‘Ž)

        i.e.

        <code>
        …
                                  // πŸ’‘The first variant, by default named "default".
                                  "nodeType": "component",
                                  "id": "uuid-here-personalization-variant-default",
                                  "type": "personalization.variant",
                                   // πŸ†• imperative choice
                                  "active": true,
                                  "slots": [
                                    {
                                      "nodeType": "slot",
                                      "id": "uuid-here-personalization-variant-default/variant",
                                      "components": [
                                        // πŸ’‘this is the original!
                                        {
                                          "nodeType": "component",
                                          "id": "baf231e8-b214-4e3e-93d3-5d3f03a1eae9",
                                          "type": "sdc.experience_builder.druplicon",
                                          "slots": []
                                        }
                                      ]
                                    }
                                  ]
        …
        

    So: thoughts? Does it make sense to you to add flags to existing client-side data model nodeTypes, or do you think it's better to instead introduce new nodeTypes?

  • πŸ‡¬πŸ‡§United Kingdom jessebaker

    +1 to using existing components and slots and just adding flags!
    +1 to the declarative choice of "segments": ["belgium", "uk", "spain"] over the imperative "active": true

    I really like the "After personalising" example but I do think it can be simplified (perhaps this is the "optimized further" that you allude to).

    In this example, instead of both a personalization-wrapper and a personalization-variant component, there is just a personalization-wrapper and it has a "mutuallyExclusiveSlots": true, flag. Then it is the slots of that component that contain the "segments": ["belgium", "uk", "spain"], information.

    ...
    {
      // πŸ’‘Wrapped in a `personalization.wrapper` component, which will contain the different variants.
      "nodeType": "component",
      "id": "uuid-here-personalization-wrapper-yo",
      "type": "personalization.wrapper",
      "mutuallyExclusiveSlots": true,
      "slots": [
        // πŸ’‘The first variant
        {
          "nodeType": "slot",
          "id": "uuid-here-personalization-wrapper-yo/variant/default",
          "segments": ["belgium", "uk", "spain"],
          "components": [
            // πŸ’‘this is the original!
            {
              "nodeType": "component",
              "id": "baf231e8-b214-4e3e-93d3-5d3f03a1eae9",
              "type": "sdc.experience_builder.druplicon",
              "slots": []
            }
          ]
        },
        // πŸ’‘The second variant
        {
          "nodeType": "slot",
          "id": "uuid-here-personalization-wrapper-yo/variant/2",
          "segments": ["uk", "finland"],
          "components": [
            // πŸ’‘this is the other version
            {
              "nodeType": "component",
              "id": "baf231e8-b214-4e3e-93d3-5d3f03a1eae9",
              "type": "sdc.experience_builder.xbicon",
              "slots": []
            }
          ]
        }
      ]
    }
    

    I have spotted a potential scenario, though, that means that perhaps mutuallyExclusiveSlots is not the correct name for the flag! In the above example an audience member in the UK might expect to see both variants at the same time and thus those are not mutallyExclusiveSlots but rather conditionalSlots.

    However, I do think there is a need for the mutallyExclusiveSlots and that is when the purpose of the variant is A/B testing. Where a site author wants 50% of their audience to see one variant and the other 50% to see the other. So perhaps both flags are needed?

  • πŸ‡ΊπŸ‡ΈUnited States effulgentsia

    Keeping the same set of nodeTypes but adding more metadata was proposed by @effulgentsia.

    +1 to using existing components and slots and just adding flags!

    Although that was my original proposal earlier this week, I changed my mind since then, and @wim leers, @penyaskito, and I met earlier today and converged on adding two new nodeTypes: switch and case to supplement the 3 that XB already has (region, component, and slot) for a total of 5. The reasoning for this is that the concept of "show only one of these depending on some logic" isn't limited to just how the resulting page gets rendered, but rather this switch/case concept needs to be integrated into the whole XB UI:

    • The layers panel needs to show only one of the variants at a time.
    • A chip needs to be added to the overlay for the active variant whenever a descendant is selected.
    • Clicking on that chip needs to open an entire React-driven (not Drupal-driven) UI in the right sidebar for configuring all of the variants and the mapping from the personalization segment to the variant.
    • From that right sidebar, you can click "Edit" on a variant that's not the one that should be shown to your currently selected audience segment, and doing so switches the canvas to a focus-mode editing experience, similar to editing global regions, where you can only work with that variant's subtree and nothing else that's on the page.

    All of that combined, but especially those last two, is a lot of logic that's specific to the concept of "here are multiple subtrees, optimize the UI for working with only one at a time, and managing which one should be active when". Having the React code implementing all of that logic based on nodeType == 'component' && type == 'personalization.wrapper' feels like breaking the simplicity of the React app basing its logic on the nodeType and leaving the details of the component "type" to the back-end.

    The idea of nodeType == 'component' && mutuallyExclusiveSlots has some appeal as the "flag" to the React app to do all the "optimize the UI for working with only one subtree at a time" things, but the downside with this is it implies that on the Drupal side, this will be managed as a single component with multiple (dynamic based on how many variants got created in the UI) slots. That might or might not be the desired back-end implementation. In fact, so far we're leaning towards a component-per-variant plus a component-for-the-group approach in order to avoid the concept of dynamic slots. Whether or not we stick with that or end up reconsidering the dynamic slots idea ideally should not require changing the client-side structure to have to match it.

    And that all leads us to the idea of adding two new nodeTypes to be explicit about the new functionality being added to the UI without being coupled to how that ends up getting stored on the server.

    With that, #5 becomes:

      "nodeType": "switch",
      "id": "UUID-of-the-Personalization-Wrapper",
      "type": "personalization",   // πŸ’‘ In the future, could add other types, such as perhaps "breakpoints".
      "mapping: {
        // πŸ’‘ Show nothing to these segments.
        "us": null,
        "uk": null,
    
        // πŸ’‘ The default variant. Shown to all segments not otherwise included in this mapping.
        "*": "UUID-of-a-Variant",
    
        // πŸ’‘ Show a different variant to these segments.
        "finland": "UUID-of-another-Variant",
        "norway": "UUID-of-another-Variant"
      },
      "cases": [
        {
          "nodeType": "case",
          "id": "UUID-of-a-Variant",
          "components": [
            // One or more components to show for this variant.
            ...
          ]
        },
        {
          "nodeType": "case",
          "id": "UUID-of-another-Variant",
          "components": [
            // One or more components to show for this variant.
            ...
          ]
        }
      ]
    }
    
    I have spotted a potential scenario, though, that means that perhaps mutuallyExclusiveSlots is not the correct name for the flag! In the above example an audience member in the UK might expect to see both variants at the same time and thus those are not mutallyExclusiveSlots but rather conditionalSlots.

    We clarified with @lauriii that this isn't what's desired in terms of design. Any given segment should only map to one variant (or to None), but multiple segments can map to the same variant. That's why in the above I moved from segments on the variant/case nodes to mapping on the wrapper/switch node.

    However, I do think there is a need for the mutallyExclusiveSlots and that is when the purpose of the variant is A/B testing. Where a site author wants 50% of their audience to see one variant and the other 50% to see the other. So perhaps both flags are needed?

    A/B testing is out of scope for the initial version of this. But in the future, we can expand mapping to accommodate it. There's probably multiple options for how to model that, but just as one naive example:

    "mapping: {
        "us": null,
        "uk": null,
        "*": "UUID-of-a-Variant",
        "finland": "UUID-of-another-Variant",
        "norway": {
          "UUID-of-a-Variant": 0.5,
          "UUID-of-another-Variant": 0.5
        }
      },
    
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    #6 Thanks so much for the detailed write-up! πŸ™ Much appreciated 😊

    Then it is the slots of that component that contain

    β€” @jessebaker in #5

    +

    this will be managed as a single component with multiple (dynamic based on how many variants got created in the UI) slots. […] we're leaning towards a component-per-variant plus a component-for-the-group approach in order to avoid the concept of dynamic slots
    

    β€” @effulgentsia in #6

    Indeed: not multiple dynamic slots, but multiple component instances (one per variant). Each of those component instances itself has a single slot, which contains the component tree for that variant. This achieves the same, without the need for

    1. making ComponentSourceWithSlotsInterface more complicated
    2. complicating the server-side rendering logic ("available slots of a component depends on logic in >=1 explicit inputs")
    3. complicating the client, because if we did B, the client would also need to be adjusted for that β€” including additional network I/O and hence latency

    Whether or not we stick with that or end up reconsidering the dynamic slots idea ideally should not require changing the client-side structure to have to match it.

    πŸ’― β€” yay for docs/adr/0005-Keep-the-front-end-simple.md 😊

Production build 0.71.5 2024