This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch 3913-grid.fixes
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit ba05dbadf3d341a78213c62115ed05b6ec102492
Author: Andi Huber <[email protected]>
AuthorDate: Thu Oct 23 17:42:32 2025 +0200

    CAUSEWAY-2297: shifting responsibilities
---
 .../apache/causeway/applib/layout/grid/Grid.java   |  49 +---
 .../causeway/applib/layout/grid/GridAbstract.java  | 210 --------------
 .../applib/layout/grid/bootstrap/BSClearFix.java   |   3 +-
 .../layout/grid/bootstrap/BSClearFixHidden.java    |   2 +-
 .../layout/grid/bootstrap/BSClearFixVisible.java   |   2 +-
 .../applib/layout/grid/bootstrap/BSCol.java        |  27 +-
 .../applib/layout/grid/bootstrap/BSElement.java    |  24 +-
 .../layout/grid/bootstrap/BSElementAbstract.java   |  21 +-
 .../applib/layout/grid/bootstrap/BSGrid.java       | 313 +++++----------------
 .../{BSClearFixHidden.java => BSGridDto.java}      |  41 ++-
 .../applib/layout/grid/bootstrap/BSRow.java        |  96 ++-----
 .../applib/layout/grid/bootstrap/BSRowContent.java |  51 +---
 .../layout/grid/bootstrap/BSRowContentOwner.java   |   2 +-
 .../applib/layout/grid/bootstrap/BSRowOwner.java   |   2 +-
 .../applib/layout/grid/bootstrap/BSTab.java        |  90 +-----
 .../applib/layout/grid/bootstrap/BSTabGroup.java   |  19 +-
 .../layout/grid/bootstrap/BSTabGroupOwner.java     |   2 +-
 .../applib/layout/grid/bootstrap/BSTabOwner.java   |   2 +-
 .../applib/layout/grid/bootstrap/BSUtil.java       |  57 ++++
 .../applib/layout/grid/bootstrap/BSWalker.java     | 182 ++++++++++++
 .../bootstrap/{HasCssId.java => HasElementId.java} |   2 +-
 .../applib/layout/grid/bootstrap/WithinGrid.java   |  30 --
 .../core/metamodel/layout/LayoutFacetUtil.java     |   2 +-
 .../services/grid/GridSystemServiceAbstract.java   |   4 +-
 .../grid/bootstrap/CollapseIfOneTabProcessor.java  |   6 +-
 .../grid/bootstrap/EmptyTabRemovalProcessor.java   |  15 +-
 .../grid/bootstrap/GridConverterFromDto.java       |  78 +++++
 .../grid/bootstrap/GridConverterToDto.java         |  29 +-
 .../bootstrap/GridMarshallerServiceBootstrap.java  |  44 ++-
 .../bootstrap/{_GridModel.java => GridModel.java}  |  38 +--
 .../grid/bootstrap/GridSystemServiceBootstrap.java |  14 +-
 .../sitemap/SitemapServiceDefault.java             |   4 +-
 .../help/topics/welcome/WelcomeHelpPage.java       |   2 +-
 .../resources/DomainObjectResourceServerside.java  |   2 +-
 .../serviceactions/MenuActionPanel.java            |   4 +-
 .../wicket/ui/components/layout/bs/col/Col.java    |   5 +-
 .../components/layout/bs/tabs/TabGroupPanel.java   |   3 +-
 37 files changed, 592 insertions(+), 885 deletions(-)

diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/Grid.java 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/Grid.java
index 30901bafcda..590b5248598 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/Grid.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/Grid.java
@@ -31,24 +31,17 @@
 /**
  * All top-level page layout classes should implement this interface.
  *
- * <p>
- *     It is used by the {@link LayoutService} as a common based type for any 
layouts read in from XML.
- * </p>
+ * <p>It is used by the {@link LayoutService} as a common based type for any 
layouts read in from XML.
  *
  * @since 1.x {@index}
  */
+@Programmatic
 public interface Grid {
 
-    @Programmatic
     Class<?> getDomainClass();
-
-    @Programmatic
     void setDomainClass(final Class<?> domainClass);
 
-    @Programmatic
     String getTnsAndSchemaLocation();
-
-    @Programmatic
     void setTnsAndSchemaLocation(final String tnsAndSchemaLocation);
 
     /**
@@ -59,50 +52,22 @@ public interface Grid {
      * Governs meta-model facet precedence, that is,
      * facets from annotations should overrule those from fallback XML grids.
      */
-    @Programmatic
     default boolean isFallback() { return false; }
 
-    @Programmatic
     boolean isNormalized();
-
-    @Programmatic
     void setNormalized(final boolean normalized);
 
-    @Programmatic
     LinkedHashMap<String, PropertyLayoutData> getAllPropertiesById();
-
-    @Programmatic
     LinkedHashMap<String, CollectionLayoutData> getAllCollectionsById();
-
-    @Programmatic
     LinkedHashMap<String, ActionLayoutData> getAllActionsById();
 
     interface Visitor {
-        void visit(final DomainObjectLayoutData domainObjectLayoutData);
-
-        void visit(final ActionLayoutData actionLayoutData);
-
-        void visit(final PropertyLayoutData propertyLayoutData);
-
-        void visit(final CollectionLayoutData collectionLayoutData);
-
-        void visit(final FieldSet fieldSet);
+        default void visit(final DomainObjectLayoutData 
domainObjectLayoutData) {}
+        default void visit(final ActionLayoutData actionLayoutData) {}
+        default void visit(final PropertyLayoutData propertyLayoutData) {}
+        default void visit(final CollectionLayoutData collectionLayoutData) {}
+        default void visit(final FieldSet fieldSet) {}
     }
 
-    class VisitorAdapter implements Visitor {
-        @Override public void visit(final DomainObjectLayoutData 
domainObjectLayoutData) {
-        }
-        @Override public void visit(final ActionLayoutData actionLayoutData) {
-        }
-        @Override public void visit(final PropertyLayoutData 
propertyLayoutData) {
-        }
-        @Override public void visit(final CollectionLayoutData 
collectionLayoutData) {
-        }
-        @Override public void visit(final FieldSet fieldSet) {
-        }
-    }
-
-    @Programmatic
     void visit(final Grid.Visitor visitor);
-
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/GridAbstract.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/GridAbstract.java
deleted file mode 100644
index 4cf7468008d..00000000000
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/GridAbstract.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.causeway.applib.layout.grid;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-
-import jakarta.xml.bind.annotation.XmlTransient;
-
-import org.apache.causeway.applib.annotation.Programmatic;
-import org.apache.causeway.applib.layout.component.ActionLayoutData;
-import org.apache.causeway.applib.layout.component.ActionLayoutDataOwner;
-import org.apache.causeway.applib.layout.component.CollectionLayoutData;
-import org.apache.causeway.applib.layout.component.CollectionLayoutDataOwner;
-import org.apache.causeway.applib.layout.component.FieldSet;
-import org.apache.causeway.applib.layout.component.FieldSetOwner;
-import org.apache.causeway.applib.layout.component.PropertyLayoutData;
-import org.apache.causeway.applib.layout.grid.bootstrap.BSGrid;
-import org.apache.causeway.applib.services.layout.LayoutService;
-
-/**
- * All top-level page layout classes should implement this interface.
- *
- * <p>
- *     It is used by the {@link LayoutService} as a common based type for any 
layouts read in from XML.
- * </p>
- *
- * @since 1.x {@index}
- */
-@XmlTransient // ignore this class
-public abstract class GridAbstract implements Grid {
-
-    private Class<?> domainClass;
-
-    @Override
-    @Programmatic
-    @XmlTransient
-    public Class<?> getDomainClass() {
-        return domainClass;
-    }
-
-    @Override
-    @Programmatic
-    public void setDomainClass(final Class<?> domainClass) {
-        this.domainClass = domainClass;
-    }
-
-    private String tnsAndSchemaLocation;
-    @Override
-    @Programmatic
-    @XmlTransient
-    public String getTnsAndSchemaLocation() {
-        return tnsAndSchemaLocation;
-    }
-
-    @Override
-    @Programmatic
-    public void setTnsAndSchemaLocation(final String tnsAndSchemaLocation) {
-        this.tnsAndSchemaLocation = tnsAndSchemaLocation;
-    }
-
-    private boolean fallback;
-    @Override
-    @Programmatic
-    @XmlTransient
-    public boolean isFallback() {
-        return fallback;
-    }
-    @Programmatic
-    public void setFallback(final boolean fallback) {
-        this.fallback = fallback;
-    }
-
-    private boolean normalized;
-    @Override
-    @Programmatic
-    @XmlTransient
-    public boolean isNormalized() {
-        return normalized;
-    }
-    @Override
-    @Programmatic
-    public void setNormalized(final boolean normalized) {
-        this.normalized = normalized;
-    }
-
-    /**
-     * Convenience for subclasses.
-     */
-    protected void traverseActions(
-            final ActionLayoutDataOwner actionLayoutDataOwner,
-            final GridAbstract.Visitor visitor) {
-
-        final List<ActionLayoutData> actionLayoutDatas = 
actionLayoutDataOwner.getActions();
-        if(actionLayoutDatas == null) {
-            return;
-        }
-        for (final ActionLayoutData actionLayoutData : new 
ArrayList<>(actionLayoutDatas)) {
-            actionLayoutData.setOwner(actionLayoutDataOwner);
-            visitor.visit(actionLayoutData);
-        }
-    }
-
-    /**
-     * Convenience for subclasses.
-     */
-    protected void traverseFieldSets(final FieldSetOwner fieldSetOwner, final 
GridAbstract.Visitor visitor) {
-        final List<FieldSet> fieldSets = fieldSetOwner.getFieldSets();
-        for (FieldSet fieldSet : new ArrayList<>(fieldSets)) {
-            fieldSet.setOwner(fieldSetOwner);
-            visitor.visit(fieldSet);
-            traverseActions(fieldSet, visitor);
-            final List<PropertyLayoutData> properties = 
fieldSet.getProperties();
-            for (final PropertyLayoutData property : new 
ArrayList<>(properties)) {
-                property.setOwner(fieldSet);
-                visitor.visit(property);
-                traverseActions(property, visitor);
-            }
-        }
-    }
-
-    /**
-     * Convenience for subclasses.
-     */
-    protected void traverseCollections(
-            final CollectionLayoutDataOwner owner, final GridAbstract.Visitor 
visitor) {
-        final List<CollectionLayoutData> collections = owner.getCollections();
-        for (CollectionLayoutData collection : new ArrayList<>(collections)) {
-            collection.setOwner(owner);
-            visitor.visit(collection);
-            traverseActions(collection, visitor);
-        }
-    }
-
-    @Override
-    @Programmatic
-    @XmlTransient
-    public LinkedHashMap<String, PropertyLayoutData> getAllPropertiesById() {
-        final LinkedHashMap<String, PropertyLayoutData> propertiesById = new 
LinkedHashMap<>();
-        visit(new BSGrid.VisitorAdapter() {
-            @Override
-            public void visit(final PropertyLayoutData propertyLayoutData) {
-                propertiesById.put(propertyLayoutData.getId(), 
propertyLayoutData);
-            }
-        });
-        return propertiesById;
-    }
-
-    @Override
-    @Programmatic
-    @XmlTransient
-    public LinkedHashMap<String, CollectionLayoutData> getAllCollectionsById() 
{
-        final LinkedHashMap<String, CollectionLayoutData> collectionsById = 
new LinkedHashMap<>();
-
-        visit(new BSGrid.VisitorAdapter() {
-            @Override
-            public void visit(final CollectionLayoutData collectionLayoutData) 
{
-                collectionsById.put(collectionLayoutData.getId(), 
collectionLayoutData);
-            }
-        });
-        return collectionsById;
-    }
-
-    @Override
-    @Programmatic
-    @XmlTransient
-    public LinkedHashMap<String, ActionLayoutData> getAllActionsById() {
-        final LinkedHashMap<String, ActionLayoutData> actionsById = new 
LinkedHashMap<>();
-
-        visit(new BSGrid.VisitorAdapter() {
-            @Override
-            public void visit(final ActionLayoutData actionLayoutData) {
-                actionsById.put(actionLayoutData.getId(), actionLayoutData);
-            }
-        });
-        return actionsById;
-    }
-
-    @Programmatic
-    @XmlTransient
-    public LinkedHashMap<String, FieldSet> getAllFieldSetsByName() {
-        final LinkedHashMap<String, FieldSet> fieldSetsByName = new 
LinkedHashMap<>();
-
-        visit(new BSGrid.VisitorAdapter() {
-            @Override
-            public void visit(final FieldSet fieldSet) {
-                fieldSetsByName.put(fieldSet.getName(), fieldSet);
-            }
-        });
-        return fieldSetsByName;
-    }
-
-}
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFix.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFix.java
index 13355954d80..172aeae3315 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFix.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFix.java
@@ -21,7 +21,8 @@
 /**
  * @since 1.x {@index}
  */
-public abstract class BSClearFix extends BSRowContent {
+public sealed abstract class BSClearFix extends BSRowContent
+permits BSClearFixHidden, BSClearFixVisible {
 
     private static final long serialVersionUID = 1L;
 
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFixHidden.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFixHidden.java
index 08458d72d5c..d62fc449caf 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFixHidden.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFixHidden.java
@@ -37,7 +37,7 @@
 @XmlType(
         name = "clearFixHidden"
         )
-public class BSClearFixHidden extends BSClearFix {
+public final class BSClearFixHidden extends BSClearFix {
 
     private static final long serialVersionUID = 1L;
 
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFixVisible.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFixVisible.java
index 2a6ed660694..1e37d7cfaa3 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFixVisible.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFixVisible.java
@@ -38,7 +38,7 @@
 @XmlType(
         name = "clearFixVisible"
         )
-public class BSClearFixVisible extends BSClearFix {
+public final class BSClearFixVisible extends BSClearFix {
 
     private static final long serialVersionUID = 1L;
 
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSCol.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSCol.java
index c8ac69fec98..4c16460e887 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSCol.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSCol.java
@@ -41,24 +41,19 @@
 /**
  * A column within a row which, depending on its {@link #getSpan()}, could be 
as narrow as 1/12th of the page's width, all the way up to spanning the entire 
page.
  *
- * <p>
- *     Pretty much other content can be contained within a column, though most 
commonly it will be {@link FieldSet fieldset}s
- *     (a group of properties) or {@link CollectionLayoutData collection}s.  
However, columns can also be used to
- *     contain further {@link BSRow row}s (creating a nested grid of 
rows/cols/rows/cols) and {@link BSTabGroup tabgroup}s.
- * </p>
+ * <p>Pretty much other content can be contained within a column, though most 
commonly it will be {@link FieldSet fieldset}s
+ * (a group of properties) or {@link CollectionLayoutData collection}s.  
However, columns can also be used to
+ * contain further {@link BSRow row}s (creating a nested grid of 
rows/cols/rows/cols) and {@link BSTabGroup tabgroup}s.
  *
- * <p>
- *     It is rendered as a (eg) &lt;div class=&quot;col-md-4 ...&quot;&gt;
- * </p>
+ * <p>It is rendered as a (eg) &lt;div class=&quot;col-md-4 ...&quot;&gt;
  *
  * @since 1.x {@index}
  */
 @XmlRootElement(
-        name = "col"
-        )
+        name = "col")
 @XmlType(
-        name = "col"
-        , propOrder = {
+        name = "col",
+        propOrder = {
                 "sizeSpans",
                 "domainObject",
                 "actions",
@@ -66,11 +61,9 @@
                 "tabGroups",
                 "fieldSets",
                 "collections",
-                "metadataError"
-        }
-        )
-public class BSCol extends BSRowContent
-implements ActionLayoutDataOwner, BSTabGroupOwner, BSRowOwner, FieldSetOwner, 
HasCssId,
+                "metadataError"})
+public final class BSCol extends BSRowContent
+implements ActionLayoutDataOwner, BSTabGroupOwner, BSRowOwner, FieldSetOwner, 
HasElementId,
 CollectionLayoutDataOwner, DomainObjectLayoutDataOwner {
 
     private static final long serialVersionUID = 1L;
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSElement.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSElement.java
index 382dfa890f4..cf75b3ae304 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSElement.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSElement.java
@@ -20,10 +20,12 @@
 
 import java.io.Serializable;
 
+import org.apache.causeway.applib.layout.grid.Grid;
+
 /**
  * @since 1.x {@index}
  */
-public interface BSElement extends WithinGrid, Serializable {
+public interface BSElement extends Serializable {
 
     /**
      * Any additional CSS classes to render on the page element corresponding 
to this object,
@@ -31,7 +33,25 @@ public interface BSElement extends WithinGrid, Serializable {
      * custom styling.
      */
     String getCssClass();
-
     void setCssClass(final String cssClass);
 
+    public interface Visitor extends Grid.Visitor {
+        default void preVisit(final BSGrid bsGrid) {}
+        default void visit(final BSGrid bsGrid) {}
+        default void postVisit(final BSGrid bsGrid) {}
+        default void preVisit(final BSRow bsRow) {}
+        default void visit(final BSRow bsRow) {}
+        default void postVisit(final BSRow bsRow) {}
+        default void preVisit(final BSCol bsCol) {}
+        default void visit(final BSCol bsCol) {}
+        default void postVisit(final BSCol bsCol) {}
+        default void visit(final BSClearFix bsClearFix) {}
+        default void preVisit(final BSTabGroup bsTabGroup) {}
+        default void visit(final BSTabGroup bsTabGroup) {}
+        default void postVisit(final BSTabGroup bsTabGroup) {}
+        default void preVisit(final BSTab bsTab) {}
+        default void visit(final BSTab bsTab) {}
+        default void postVisit(final BSTab bsTab) {}
+    }
+
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSElementAbstract.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSElementAbstract.java
index 66996801d5e..f5fe6833a27 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSElementAbstract.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSElementAbstract.java
@@ -20,31 +20,26 @@
 
 import jakarta.xml.bind.annotation.XmlAttribute;
 
+import lombok.Getter;
+import lombok.Setter;
+
 /**
  * Superclass for all layout classes, factoring out the common {@link 
#getCssClass()} attribute.
  *
  * @since 1.x {@index}
  */
-public abstract class BSElementAbstract implements BSElement {
+public sealed abstract class BSElementAbstract implements BSElement
+permits BSRow, BSRowContent, BSTab, BSTabGroup {
 
     private static final long serialVersionUID = 1L;
 
-    private String cssClass;
-
     /**
      * Any additional CSS classes to render on the page element corresponding 
to this object,
      * eg as per the <a 
href="http://getbootstrap.com/css/#grid-less";>Bootstrap mixins</a> or just for
      * custom styling.
      */
-    @Override
-    @XmlAttribute(required = false)
-    public String getCssClass() {
-        return cssClass;
-    }
-
-    @Override
-    public void setCssClass(final String cssClass) {
-        this.cssClass = cssClass;
-    }
+    @Getter(onMethod_ = {@XmlAttribute(required = false)})
+    @Setter
+    private String cssClass;
 
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSGrid.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSGrid.java
index 144142be7a1..7246973dec1 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSGrid.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSGrid.java
@@ -22,274 +22,115 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 
-import jakarta.xml.bind.annotation.XmlAttribute;
-import jakarta.xml.bind.annotation.XmlElement;
-import jakarta.xml.bind.annotation.XmlRootElement;
-import jakarta.xml.bind.annotation.XmlTransient;
-import jakarta.xml.bind.annotation.XmlType;
-
-import org.apache.causeway.applib.annotation.Programmatic;
 import org.apache.causeway.applib.layout.component.ActionLayoutData;
 import org.apache.causeway.applib.layout.component.CollectionLayoutData;
-import org.apache.causeway.applib.layout.component.DomainObjectLayoutData;
-import org.apache.causeway.applib.layout.component.FieldSet;
 import org.apache.causeway.applib.layout.component.PropertyLayoutData;
 import org.apache.causeway.applib.layout.grid.Grid;
-import org.apache.causeway.applib.layout.grid.GridAbstract;
 import org.apache.causeway.applib.mixins.dto.Dto;
 
+import lombok.Getter;
+import lombok.Setter;
+
 /**
- * This is the top-level for rendering the domain object's properties, 
collections and actions.  It simply consists
- * of a number of rows.
- *
- * <p>
- *     The element is rendered as a &lt;div class=&quot;...&quot;&gt;
- * </p>
+ * This is the top-level for rendering the domain object's properties, 
collections and actions.
+ * It simply consists of a number of rows.
  *
  * @since 1.x {@index}
  */
-@XmlRootElement(
-        name = "grid"
-        )
-@XmlType(
-        name = "grid"
-        , propOrder = {
-                "rows",
-                "metadataErrors"
-        }
-        )
-public class BSGrid extends GridAbstract implements BSElement, Dto, BSRowOwner 
{
+public final class BSGrid implements Grid, BSElement, Dto, BSRowOwner {
 
     private static final long serialVersionUID = 1L;
 
-    private String cssClass;
-
-    @Override
-    @XmlAttribute(required = false)
-    public String getCssClass() {
-        return cssClass;
-    }
-
-    @Override
-    public void setCssClass(final String cssClass) {
-        this.cssClass = cssClass;
-    }
-
-    private List<BSRow> rows = new ArrayList<>();
-
-    // no wrapper
-    @Override
-    @XmlElement(name = "row", required = true)
-    public List<BSRow> getRows() {
-        return rows;
-    }
-
-    public void setRows(final List<BSRow> rows) {
-        this.rows = rows;
-    }
-
-    private List<String> metadataErrors = new ArrayList<>();
+    @Getter @Setter private Class<?> domainClass;
+    @Getter @Setter private String tnsAndSchemaLocation;
+    @Getter @Setter private boolean fallback;
+    @Getter @Setter private boolean normalized;
+    @Getter @Setter private String cssClass;
 
+    @Getter private final List<BSRow> rows = new ArrayList<>();
     /**
      * For diagnostics; populated by the framework if and only if a metadata 
error.
      */
-    @XmlElement(name = "metadataError", required = false)
-    public List<String> getMetadataErrors() {
-        return metadataErrors;
-    }
-
-    public void setMetadataErrors(final List<String> metadataErrors) {
-        this.metadataErrors = metadataErrors;
-    }
-
-    @SuppressWarnings("unused")
-    private BSRowOwner owner;
-
-    public interface Visitor extends Grid.Visitor {
-        void preVisit(final BSGrid bsGrid);
-        void visit(final BSGrid bsGrid);
-        void postVisit(final BSGrid bsGrid);
-        void preVisit(final BSRow bsRow);
-        void visit(final BSRow bsRow);
-        void postVisit(final BSRow bsRow);
-        void preVisit(final BSCol bsCol);
-        void visit(final BSCol bsCol);
-        void postVisit(final BSCol bsCol);
-        void visit(final BSClearFix bsClearFix);
-        void preVisit(final BSTabGroup bsTabGroup);
-        void visit(final BSTabGroup bsTabGroup);
-        void postVisit(final BSTabGroup bsTabGroup);
-        void preVisit(final BSTab bsTab);
-        void visit(final BSTab bsTab);
-        void postVisit(final BSTab bsTab);
-    }
-
-    public static class VisitorAdapter extends Grid.VisitorAdapter implements 
Visitor {
-        @Override public void preVisit(final BSGrid bsGrid) { }
-        @Override public void visit(final BSGrid bsGrid) { }
-        @Override public void postVisit(final BSGrid bsGrid) { }
-
-        @Override public void preVisit(final BSRow bsRow) { }
-        @Override public void visit(final BSRow bsRow) { }
-        @Override public void postVisit(final BSRow bsRow) { }
-
-        @Override public void preVisit(final BSCol bsCol) { }
-        @Override public void visit(final BSCol bsCol) { }
-        @Override public void postVisit(final BSCol bsCol) { }
-
-        @Override public void visit(final BSClearFix bsClearFix) { }
-
-        @Override public void preVisit(final BSTabGroup bsTabGroup) { }
-        @Override public void visit(final BSTabGroup bsTabGroup) { }
-        @Override public void postVisit(final BSTabGroup bsTabGroup) { }
-
-        @Override public void preVisit(final BSTab bsTab) { }
-        @Override public void visit(final BSTab bsTab) { }
-        @Override public void postVisit(final BSTab bsTab) { }
-    }
+    @Getter private final List<String> metadataErrors = new ArrayList<>();
 
     @Override
     public void visit(final Grid.Visitor visitor) {
-        final BSGrid.Visitor bsVisitor = asBsVisitor(visitor);
-        bsVisitor.preVisit(this);
-        bsVisitor.visit(this);
-        traverseRows(this, visitor);
-        bsVisitor.postVisit(this);
+        new BSWalker(this).visit(visitor);
     }
 
-    protected void traverseRows(final BSRowOwner rowOwner, final Grid.Visitor 
visitor) {
-        final BSGrid.Visitor bsVisitor = asBsVisitor(visitor);
-        final List<BSRow> rows = rowOwner.getRows();
-        for (BSRow bsRow : new ArrayList<>(rows)) {
-            bsRow.setOwner(this);
-            bsVisitor.preVisit(bsRow);
-            bsVisitor.visit(bsRow);
-            traverseCols(visitor, bsRow);
-            bsVisitor.postVisit(bsRow);
-        }
-    }
-
-    private void traverseCols(final Grid.Visitor visitor, final BSRow bsRow) {
-        final BSGrid.Visitor bsVisitor = asBsVisitor(visitor);
-        final List<BSRowContent> cols = bsRow.getCols();
-        for (BSRowContent rowContent : new ArrayList<>(cols)) {
-            rowContent.setOwner(bsRow);
-            if(rowContent instanceof BSCol) {
-                final BSCol bsCol = (BSCol) rowContent;
-                bsVisitor.preVisit(bsCol);
-                bsVisitor.visit(bsCol);
-                traverseDomainObject(bsCol, visitor);
-                traverseTabGroups(bsCol, visitor);
-                traverseActions(bsCol, visitor);
-                traverseFieldSets(bsCol, visitor);
-                traverseCollections(bsCol, visitor);
-                traverseRows(bsCol, visitor);
-                bsVisitor.postVisit(bsCol);
-            } else if (rowContent instanceof BSClearFix) {
-                final BSClearFix bsClearFix = (BSClearFix) rowContent;
-                bsVisitor.visit(bsClearFix);
-            } else {
-                throw new IllegalStateException(
-                        "Unrecognized implementation of BSRowContent, " + 
rowContent);
-            }
-        }
-    }
-
-    private void traverseDomainObject(final BSCol bsCol, final Grid.Visitor 
visitor) {
-        final DomainObjectLayoutData domainObject = bsCol.getDomainObject();
-        if(domainObject == null) {
-            return;
-        }
-        domainObject.setOwner(bsCol);
-        visitor.visit(domainObject);
-    }
-
-    private void traverseTabGroups(
-            final BSTabGroupOwner bsTabGroupOwner,
-            final Grid.Visitor visitor) {
-        final BSGrid.Visitor bsVisitor = asBsVisitor(visitor);
-        final List<BSTabGroup> tabGroups = bsTabGroupOwner.getTabGroups();
-        for (BSTabGroup bsTabGroup : new ArrayList<>(tabGroups)) {
-            bsTabGroup.setOwner(bsTabGroupOwner);
-            bsVisitor.preVisit(bsTabGroup);
-            bsVisitor.visit(bsTabGroup);
-            traverseTabs(bsTabGroup, visitor);
-            bsVisitor.postVisit(bsTabGroup);
-        }
-    }
-
-    private void traverseTabs(
-            final BSTabOwner bsTabOwner,
-            final Grid.Visitor visitor) {
-        final BSGrid.Visitor bsVisitor = asBsVisitor(visitor);
-        final List<BSTab> tabs = bsTabOwner.getTabs();
-        for (BSTab tab : new ArrayList<>(tabs)) {
-            tab.setOwner(bsTabOwner);
-            bsVisitor.preVisit(tab);
-            bsVisitor.visit(tab);
-            traverseRows(tab, visitor);
-            bsVisitor.postVisit(tab);
-        }
-    }
-
-    private static Visitor asBsVisitor(final Grid.Visitor visitor) {
-        return visitor instanceof Visitor? (Visitor) visitor : new 
BSGrid.VisitorAdapter() {
-            @Override public void visit(final DomainObjectLayoutData 
domainObjectLayoutData) {
-                visitor.visit(domainObjectLayoutData);
-            }
-
-            @Override public void visit(final ActionLayoutData 
actionLayoutData) {
-                visitor.visit(actionLayoutData);
-            }
-
-            @Override public void visit(final PropertyLayoutData 
propertyLayoutData) {
-                visitor.visit(propertyLayoutData);
-            }
-
-            @Override public void visit(final CollectionLayoutData 
collectionLayoutData) {
-                visitor.visit(collectionLayoutData);
-            }
-
-            @Override public void visit(final FieldSet fieldSet) {
-                visitor.visit(fieldSet);
-            }
-        };
-    }
-
-    @Programmatic
-    @XmlTransient
-    public LinkedHashMap<String, BSTab> getAllTabsByName() {
-        final LinkedHashMap<String, BSTab> tabsByName = new LinkedHashMap<>();
-
-        visit(new BSGrid.VisitorAdapter() {
+    @Override
+    public LinkedHashMap<String, PropertyLayoutData> getAllPropertiesById() {
+        final LinkedHashMap<String, PropertyLayoutData> propertiesById = new 
LinkedHashMap<>();
+        visit(new BSElement.Visitor() {
             @Override
-            public void visit(final BSTab bSTab) {
-                tabsByName.put(bSTab.getName(), bSTab);
+            public void visit(final PropertyLayoutData propertyLayoutData) {
+                propertiesById.put(propertyLayoutData.getId(), 
propertyLayoutData);
             }
         });
-        return tabsByName;
+        return propertiesById;
     }
 
-    @Programmatic
-    @XmlTransient
-    public LinkedHashMap<String, HasCssId> getAllCssId() {
-        final LinkedHashMap<String, HasCssId> divsByCssId = new 
LinkedHashMap<>();
-
-        visit(new BSGrid.VisitorAdapter() {
+    @Override
+    public LinkedHashMap<String, CollectionLayoutData> getAllCollectionsById() 
{
+        final LinkedHashMap<String, CollectionLayoutData> collectionsById = 
new LinkedHashMap<>();
+        visit(new BSElement.Visitor() {
             @Override
-            public void visit(final BSRow bsRow) {
-                final String id = bsRow.getId();
-                divsByCssId.put(id, bsRow);
+            public void visit(final CollectionLayoutData collectionLayoutData) 
{
+                collectionsById.put(collectionLayoutData.getId(), 
collectionLayoutData);
             }
         });
-        return divsByCssId;
+        return collectionsById;
     }
 
     @Override
-    @Programmatic
-    @XmlTransient
-    public BSGrid getGrid() {
-        return this;
-    }
+    public LinkedHashMap<String, ActionLayoutData> getAllActionsById() {
+        final LinkedHashMap<String, ActionLayoutData> actionsById = new 
LinkedHashMap<>();
+        visit(new BSElement.Visitor() {
+            @Override
+            public void visit(final ActionLayoutData actionLayoutData) {
+                actionsById.put(actionLayoutData.getId(), actionLayoutData);
+            }
+        });
+        return actionsById;
+    }
+
+// -- UNUSED
+
+//  public LinkedHashMap<String, BSTab> getAllTabsByName() {
+//      final LinkedHashMap<String, BSTab> tabsByName = new LinkedHashMap<>();
+//
+//      visit(new BSGrid.Visitor() {
+//          @Override
+//          public void visit(final BSTab bSTab) {
+//              tabsByName.put(bSTab.getName(), bSTab);
+//          }
+//      });
+//      return tabsByName;
+//  }
+//
+//  public LinkedHashMap<String, HasElementId> getAllCssId() {
+//      final LinkedHashMap<String, HasElementId> divsByCssId = new 
LinkedHashMap<>();
+//
+//      visit(new BSGrid.Visitor() {
+//          @Override
+//          public void visit(final BSRow bsRow) {
+//              final String id = bsRow.getId();
+//              divsByCssId.put(id, bsRow);
+//          }
+//      });
+//      return divsByCssId;
+//  }
+//
+//    public LinkedHashMap<String, FieldSet> getAllFieldSetsByName() {
+//        final LinkedHashMap<String, FieldSet> fieldSetsByName = new 
LinkedHashMap<>();
+//
+//        visit(new BSGrid.Visitor() {
+//            @Override
+//            public void visit(final FieldSet fieldSet) {
+//                fieldSetsByName.put(fieldSet.getName(), fieldSet);
+//            }
+//        });
+//        return fieldSetsByName;
+//    }
 
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFixHidden.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSGridDto.java
similarity index 60%
copy from 
api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFixHidden.java
copy to 
api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSGridDto.java
index 08458d72d5c..c7f02262193 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSClearFixHidden.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSGridDto.java
@@ -18,32 +18,29 @@
  */
 package org.apache.causeway.applib.layout.grid.bootstrap;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import jakarta.xml.bind.annotation.XmlAttribute;
+import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import jakarta.xml.bind.annotation.XmlType;
 
-/**
- * One of the <a 
href="http://getbootstrap.com/css/#responsive-utilities";>Responsive utility 
classes</a>.
- *
- *
- * <p>
- *     It is rendered as a (eg) &lt;div class=&quot;clearfix hidden-xs 
...&quot;&gt;
- * </p>
- *
- * @since 1.x {@index}
- */
-@XmlRootElement(
-        name = "clearFixHidden"
-        )
-@XmlType(
-        name = "clearFixHidden"
-        )
-public class BSClearFixHidden extends BSClearFix {
+import lombok.Getter;
+import lombok.Setter;
 
+@XmlRootElement(name = "grid")
+@XmlType(name = "grid", propOrder = {"rows", "metadataErrors"})
+@Setter
+public class BSGridDto implements BSElement, BSRowOwner {
     private static final long serialVersionUID = 1L;
 
-    @Override
-    public String toCssClass() {
-        return "clearfix " + getDisplayFragment(CssDisplay.NONE, getSize())
-                + (getCssClass() != null? " " + getCssClass(): "");
-    }
+    @Getter(onMethod_ = {@XmlAttribute(required = false)})
+    private String cssClass;
+
+    @Getter(onMethod_ = {@XmlElement(name = "row", required = true)})
+    private List<BSRow> rows;
+
+    @Getter(onMethod_ = {@XmlElement(name = "metadataError", required = 
false)})
+    private List<String> metadataErrors = new ArrayList<>();
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRow.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRow.java
index c5c1392d261..2405af61909 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRow.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRow.java
@@ -22,109 +22,53 @@
 import java.util.List;
 
 import jakarta.xml.bind.annotation.XmlAttribute;
-import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlElementRef;
 import jakarta.xml.bind.annotation.XmlElementRefs;
-import jakarta.xml.bind.annotation.XmlTransient;
 import jakarta.xml.bind.annotation.XmlType;
 
-import org.apache.causeway.applib.annotation.Programmatic;
+import lombok.Getter;
+import lombok.Setter;
 
 /**
  * Contains a row of content, either on the top-level {@link BSGrid page} or 
at any other lower-level element that can
  * contain rows, eg {@link BSTab tabs}.
  *
- * <p>
- *     It is rendered as a &lt;div class=&quot;row ...&quot;&gt;
- * </p>
+ * <p>It is rendered as a &lt;div class=&quot;row ...&quot;&gt;
  *
  * @since 1.x {@index}
  */
 @XmlType(
-        name = "row"
-        , propOrder = {
-                "cols"
-                , "metadataError"
-        }
-        )
-public class BSRow extends BSElementAbstract implements HasCssId, 
BSRowContentOwner {
+        name = "row",
+        propOrder = {"cols", "metadataError"})
+public final class BSRow extends BSElementAbstract implements HasElementId, 
BSRowContentOwner {
 
     private static final long serialVersionUID = 1L;
 
-    private String id;
-
     /**
      * As per &lt;div id=&quot;...&quot;&gt;...&lt;/div&gt; : must be unique 
across entire page.
      */
-    @Override
-    @XmlAttribute(required = false)
-    public String getId() {
-        return id;
-    }
-
-    public void setId(final String id) {
-        this.id = id;
-    }
-
-    private List<BSRowContent> cols = new ArrayList<>();
+    @Getter(onMethod_ = {@XmlAttribute(required = false)})
+    @Setter
+    private String id;
 
-    // no wrapper
-    @XmlElementRefs({
-        @XmlElementRef(type = BSCol.class, name="col", required = true),
-        @XmlElementRef(type = BSClearFixVisible.class,  
name="clearFixVisible", required = false),
-        @XmlElementRef(type = BSClearFixHidden.class,  name="clearFixHidden", 
required = false)
+    @Getter(onMethod_ = {
+            @XmlElementRefs({
+                @XmlElementRef(type = BSCol.class, name="col", required = 
true),
+                @XmlElementRef(type = BSClearFixVisible.class,  
name="clearFixVisible", required = false),
+                @XmlElementRef(type = BSClearFixHidden.class,  
name="clearFixHidden", required = false)
+            })
     })
-    public List<BSRowContent> getCols() {
-        return cols;
-    }
-
-    public void setCols(final List<BSRowContent> cols) {
-        this.cols = cols;
-    }
-
-    private String metadataError;
+    private List<BSRowContent> cols = new ArrayList<>();
 
     /**
      * For diagnostics; populated by the framework if and only if a metadata 
error.
      */
-    @XmlElement(required = false)
-    public String getMetadataError() {
-        return metadataError;
-    }
-
-    public void setMetadataError(final String metadataError) {
-        this.metadataError = metadataError;
-    }
-
-    private BSRowOwner owner;
-
-    /**
-     * Owner.
-     *
-     * <p>
-     *     Set programmatically by framework after reading in from XML.
-     * </p>
-     */
-    @XmlTransient
-    public BSRowOwner getOwner() {
-        return owner;
-    }
-
-    public void setOwner(final BSRowOwner owner) {
-        this.owner = owner;
-    }
-
-    @Override
-    @XmlTransient
-    @Programmatic
-    public BSGrid getGrid() {
-        return getOwner().getGrid();
-    }
+    @Getter(onMethod_ = {@XmlAttribute(required = false)})
+    @Setter
+    private String metadataError;
 
     @Override public String toString() {
-        return "BSRow{" +
-                "id='" + id + '\'' +
-                '}';
+        return "BSRow{" + "id='" + id + '\'' + '}';
     }
 
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowContent.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowContent.java
index 51dcacb2ed0..137e4df57bb 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowContent.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowContent.java
@@ -19,61 +19,28 @@
 package org.apache.causeway.applib.layout.grid.bootstrap;
 
 import jakarta.xml.bind.annotation.XmlAttribute;
-import jakarta.xml.bind.annotation.XmlTransient;
 
-import org.apache.causeway.applib.annotation.Programmatic;
+import lombok.Getter;
+import lombok.Setter;
 
 /**
  * Common superclass for any content of a row.
  *
- * <p>
- *     Most commonly the content of a row is {@link BSCol col}umns, but it may 
be either of the
- *     {@link BSClearFix clearfix} classes.
- * </p>
+ * <p> Most commonly the content of a row is {@link BSCol col}umns, but it may 
be either of the
+ * {@link BSClearFix clearfix} classes.
  *
  * @since 1.x {@index}
  */
-public abstract class BSRowContent extends BSElementAbstract {
+public sealed abstract class BSRowContent extends BSElementAbstract
+permits BSCol, BSClearFix {
 
     private static final long serialVersionUID = 1L;
 
-    private Size size;
-
     /**
      * Default if not specified is {@link Size#MD}.
      */
-    @XmlAttribute(required = false)
-    public Size getSize() {
-        return size;
-    }
-
-    public void setSize(final Size size) {
-        this.size = size;
-    }
-
-    private BSRowContentOwner owner;
-
-    /**
-     * Owner.
-     *
-     * <p>
-     *     Set programmatically by framework after reading in from XML.
-     * </p>
-     */
-    @XmlTransient
-    public BSRowContentOwner getOwner() {
-        return owner;
-    }
-
-    public void setOwner(final BSRowContentOwner owner) {
-        this.owner = owner;
-    }
-
-    @Override
-    @XmlTransient
-    @Programmatic
-    public BSGrid getGrid() {
-        return getOwner().getGrid();
-    }
+    @Getter(onMethod_ = {@XmlAttribute(required = false)})
+    @Setter
+    private Size size;
 
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowContentOwner.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowContentOwner.java
index be3c51d8d3e..cc69a5b65ef 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowContentOwner.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowContentOwner.java
@@ -23,6 +23,6 @@
 /**
  * @since 1.x {@index}
  */
-public interface BSRowContentOwner extends Owner, WithinGrid {
+public interface BSRowContentOwner extends Owner {
 
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowOwner.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowOwner.java
index 034417c51ab..f62bc730a2d 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowOwner.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSRowOwner.java
@@ -25,7 +25,7 @@
 /**
  * @since 1.x {@index}
  */
-public interface BSRowOwner extends Owner, WithinGrid {
+public interface BSRowOwner extends Owner {
 
     List<BSRow> getRows();
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTab.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTab.java
index 617ba73d9f0..eac5aa463f0 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTab.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTab.java
@@ -20,37 +20,24 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Predicate;
-
 import jakarta.xml.bind.annotation.XmlAttribute;
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlTransient;
 import jakarta.xml.bind.annotation.XmlType;
 
 import org.apache.causeway.applib.annotation.Programmatic;
-import org.apache.causeway.applib.layout.component.ActionLayoutData;
-import org.apache.causeway.applib.layout.component.CollectionLayoutData;
-import org.apache.causeway.applib.layout.component.DomainObjectLayoutData;
-import org.apache.causeway.applib.layout.component.PropertyLayoutData;
 
 /**
  * Represents a tab within a {@link BSTabGroup tab group}.
  *
- * <p>
- *     They simply contain one or more {@link BSRow row}s.
- * </p>
+ * <p>They simply contain one or more {@link BSRow row}s.
  *
  * @since 1.x {@index}
  */
 @XmlType(
-        name = "tab"
-        , propOrder = {
-                "name",
-                "rows"
-        }
-        )
-public class BSTab extends BSElementAbstract implements BSRowOwner {
+        name = "tab",
+        propOrder = {"name", "rows"})
+public final class BSTab extends BSElementAbstract implements BSRowOwner {
 
     private static final long serialVersionUID = 1L;
 
@@ -95,75 +82,6 @@ public void setOwner(final BSTabOwner owner) {
         this.owner = owner;
     }
 
-    public static class Predicates {
-        public static Predicate<BSTab> notEmpty() {
-            final AtomicBoolean visitingTheNode = new AtomicBoolean(false);
-            final AtomicBoolean foundContent = new AtomicBoolean(false);
-
-            return thisBsTab -> {
-                final BSGrid owningGrid = thisBsTab.getGrid();
-                owningGrid.visit(new BSGrid.VisitorAdapter() {
-
-                    /**
-                     * if found the tab, then reset 'foundContent' to false, 
and then use 'visitingTheNode' as
-                     * a marker to indicate that the visitor is now being 
passed to the nodes underneath the tab.
-                     * In those children, if visited (with the 
'visitingTheNode' flag enabled), then simply set the
-                     * 'foundContent' flag.
-                     */
-                    @Override
-                    public void preVisit(final BSTab bsTab) {
-                        if(bsTab == thisBsTab) {
-                            foundContent.set(false);
-                            visitingTheNode.set(true);
-                        }
-                    }
-
-                    @Override public void postVisit(final BSTab bsTab) {
-                        if(bsTab == thisBsTab) {
-                            visitingTheNode.set(false);
-                        }
-                    }
-
-                    @Override
-                    public void visit(final DomainObjectLayoutData 
domainObjectLayoutData) {
-                        if(visitingTheNode.get()) {
-                            foundContent.set(true);
-                        }
-                    }
-
-                    @Override
-                    public void visit(final ActionLayoutData actionLayoutData) 
{
-                        if(visitingTheNode.get()) {
-                            foundContent.set(true);
-                        }
-                    }
-
-                    @Override
-                    public void visit(final PropertyLayoutData 
propertyLayoutData) {
-                        if(visitingTheNode.get()) {
-                            foundContent.set(true);
-                        }
-                    }
-
-                    @Override
-                    public void visit(final CollectionLayoutData 
collectionLayoutData) {
-                        if(visitingTheNode.get()) {
-                            foundContent.set(true);
-                        }
-                    }
-                });
-                return foundContent.get();
-            };
-        }
-    }
-
-    @Override
-    @XmlTransient
-    @Programmatic
-    public BSGrid getGrid() {
-        return getOwner().getGrid();
-    }
-
     /**
      * removes this tab from its tab-group
      */
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabGroup.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabGroup.java
index 16ee03d61fc..3d508a5f3bd 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabGroup.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabGroup.java
@@ -26,21 +26,17 @@
 import jakarta.xml.bind.annotation.XmlTransient;
 import jakarta.xml.bind.annotation.XmlType;
 
-import org.apache.causeway.applib.annotation.Programmatic;
-
 /**
  * Represents a tab group containing one or more {@link BSTab tab}s.
  *
  * @since 1.x {@index}
  */
 @XmlType(
-        name = "tabGroup"
-        , propOrder = {
+        name = "tabGroup",
+        propOrder = {
                 "tabs",
-                "metadataError"
-        }
-        )
-public class BSTabGroup extends BSElementAbstract implements BSTabOwner {
+                "metadataError"})
+public final class BSTabGroup extends BSElementAbstract implements BSTabOwner {
 
     private static final long serialVersionUID = 1L;
 
@@ -119,11 +115,4 @@ public void setMetadataError(final String metadataError) {
         this.metadataError = metadataError;
     }
 
-    @Override
-    @XmlTransient
-    @Programmatic
-    public BSGrid getGrid() {
-        return getOwner().getGrid();
-    }
-
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabGroupOwner.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabGroupOwner.java
index 0fe167965c4..e126c4be9ff 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabGroupOwner.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabGroupOwner.java
@@ -25,7 +25,7 @@
 /**
  * @since 1.x {@index}
  */
-public interface BSTabGroupOwner extends Owner, WithinGrid {
+public interface BSTabGroupOwner extends Owner {
 
     List<BSTabGroup> getTabGroups();
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabOwner.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabOwner.java
index 6647b35550c..2f029798cbe 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabOwner.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTabOwner.java
@@ -25,7 +25,7 @@
 /**
  * @since 1.x {@index}
  */
-public interface BSTabOwner extends Owner, WithinGrid {
+public interface BSTabOwner extends Owner {
 
     List<BSTab> getTabs();
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSUtil.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSUtil.java
new file mode 100644
index 00000000000..4eb1f374cca
--- /dev/null
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSUtil.java
@@ -0,0 +1,57 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.applib.layout.grid.bootstrap;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.causeway.applib.layout.component.ActionLayoutData;
+import org.apache.causeway.applib.layout.component.CollectionLayoutData;
+import org.apache.causeway.applib.layout.component.DomainObjectLayoutData;
+import org.apache.causeway.applib.layout.component.PropertyLayoutData;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class BSUtil {
+
+    public boolean hasContent(final BSTab thisBsTab) {
+        final AtomicBoolean foundContent = new AtomicBoolean(false);
+        new BSWalker(thisBsTab).visit(new BSElement.Visitor() {
+            @Override
+            public void visit(final DomainObjectLayoutData 
domainObjectLayoutData) {
+                foundContent.set(true);
+            }
+            @Override
+            public void visit(final ActionLayoutData actionLayoutData) {
+                foundContent.set(true);
+            }
+            @Override
+            public void visit(final PropertyLayoutData propertyLayoutData) {
+                foundContent.set(true);
+            }
+            @Override
+            public void visit(final CollectionLayoutData collectionLayoutData) 
{
+                foundContent.set(true);
+            }
+        });
+        return foundContent.get();
+    }
+
+
+}
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSWalker.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSWalker.java
new file mode 100644
index 00000000000..339853218da
--- /dev/null
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSWalker.java
@@ -0,0 +1,182 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.applib.layout.grid.bootstrap;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.causeway.applib.layout.component.ActionLayoutData;
+import org.apache.causeway.applib.layout.component.ActionLayoutDataOwner;
+import org.apache.causeway.applib.layout.component.CollectionLayoutData;
+import org.apache.causeway.applib.layout.component.CollectionLayoutDataOwner;
+import org.apache.causeway.applib.layout.component.DomainObjectLayoutData;
+import org.apache.causeway.applib.layout.component.FieldSet;
+import org.apache.causeway.applib.layout.component.FieldSetOwner;
+import org.apache.causeway.applib.layout.component.PropertyLayoutData;
+import org.apache.causeway.applib.layout.grid.Grid;
+
+public record BSWalker(BSRowOwner root) {
+
+    public void visit(final Grid.Visitor visitor) {
+        final BSElement.Visitor bsVisitor = asBsVisitor(visitor);
+        if(root instanceof BSGrid bsGrid) {
+            bsVisitor.preVisit(bsGrid);
+            bsVisitor.visit(bsGrid);
+            traverseRows(root, bsVisitor);
+            bsVisitor.postVisit(bsGrid);
+        } else {
+            traverseRows(root, bsVisitor);
+        }
+    }
+
+    private void traverseRows(final BSRowOwner rowOwner, final 
BSElement.Visitor visitor) {
+        final BSElement.Visitor bsVisitor = asBsVisitor(visitor);
+        final List<BSRow> rows = rowOwner.getRows();
+        for (BSRow bsRow : new ArrayList<>(rows)) {
+            bsVisitor.preVisit(bsRow);
+            bsVisitor.visit(bsRow);
+            traverseCols(bsRow, visitor);
+            bsVisitor.postVisit(bsRow);
+        }
+    }
+
+    private void traverseCols(final BSRow bsRow, final BSElement.Visitor 
visitor) {
+        final BSElement.Visitor bsVisitor = asBsVisitor(visitor);
+        final List<BSRowContent> cols = bsRow.getCols();
+        for (BSRowContent rowContent : new ArrayList<>(cols)) {
+            if(rowContent instanceof BSCol) {
+                final BSCol bsCol = (BSCol) rowContent;
+                bsVisitor.preVisit(bsCol);
+                bsVisitor.visit(bsCol);
+                traverseDomainObject(bsCol, visitor);
+                traverseTabGroups(bsCol, visitor);
+                traverseActions(bsCol, visitor);
+                traverseFieldSets(bsCol, visitor);
+                traverseCollections(bsCol, visitor);
+                traverseRows(bsCol, visitor);
+                bsVisitor.postVisit(bsCol);
+            } else if (rowContent instanceof BSClearFix) {
+                final BSClearFix bsClearFix = (BSClearFix) rowContent;
+                bsVisitor.visit(bsClearFix);
+            } else {
+                throw new IllegalStateException(
+                        "Unrecognized implementation of BSRowContent, " + 
rowContent);
+            }
+        }
+    }
+
+    private void traverseDomainObject(final BSCol bsCol, final 
BSElement.Visitor visitor) {
+        final DomainObjectLayoutData domainObject = bsCol.getDomainObject();
+        if(domainObject == null) return;
+
+        domainObject.setOwner(bsCol);
+        visitor.visit(domainObject);
+    }
+
+    private void traverseTabGroups(final BSTabGroupOwner bsTabGroupOwner, 
final BSElement.Visitor visitor) {
+        final BSElement.Visitor bsVisitor = asBsVisitor(visitor);
+        final List<BSTabGroup> tabGroups = bsTabGroupOwner.getTabGroups();
+        for (BSTabGroup bsTabGroup : new ArrayList<>(tabGroups)) {
+            bsTabGroup.setOwner(bsTabGroupOwner);
+            bsVisitor.preVisit(bsTabGroup);
+            bsVisitor.visit(bsTabGroup);
+            traverseTabs(bsTabGroup, visitor);
+            bsVisitor.postVisit(bsTabGroup);
+        }
+    }
+
+    private void traverseTabs(final BSTabOwner bsTabOwner, final 
BSElement.Visitor visitor) {
+        final BSElement.Visitor bsVisitor = asBsVisitor(visitor);
+        final List<BSTab> tabs = bsTabOwner.getTabs();
+        for (BSTab tab : new ArrayList<>(tabs)) {
+            tab.setOwner(bsTabOwner);
+            bsVisitor.preVisit(tab);
+            bsVisitor.visit(tab);
+            traverseRows(tab, visitor);
+            bsVisitor.postVisit(tab);
+        }
+    }
+
+    /**
+     * Convenience for subclasses.
+     */
+    private void traverseActions(final ActionLayoutDataOwner 
actionLayoutDataOwner, final BSElement.Visitor visitor) {
+        final List<ActionLayoutData> actionLayoutDatas = 
actionLayoutDataOwner.getActions();
+        if(actionLayoutDatas == null) return;
+
+        for (final ActionLayoutData actionLayoutData : new 
ArrayList<>(actionLayoutDatas)) {
+            actionLayoutData.setOwner(actionLayoutDataOwner);
+            visitor.visit(actionLayoutData);
+        }
+    }
+
+    /**
+     * Convenience for subclasses.
+     */
+    private void traverseFieldSets(final FieldSetOwner fieldSetOwner, final 
BSElement.Visitor visitor) {
+        final List<FieldSet> fieldSets = fieldSetOwner.getFieldSets();
+        for (FieldSet fieldSet : new ArrayList<>(fieldSets)) {
+            fieldSet.setOwner(fieldSetOwner);
+            visitor.visit(fieldSet);
+            traverseActions(fieldSet, visitor);
+            final List<PropertyLayoutData> properties = 
fieldSet.getProperties();
+            for (final PropertyLayoutData property : new 
ArrayList<>(properties)) {
+                property.setOwner(fieldSet);
+                visitor.visit(property);
+                traverseActions(property, visitor);
+            }
+        }
+    }
+
+    /**
+     * Convenience for subclasses.
+     */
+    private void traverseCollections(
+            final CollectionLayoutDataOwner owner, final BSElement.Visitor 
visitor) {
+        final List<CollectionLayoutData> collections = owner.getCollections();
+        for (CollectionLayoutData collection : new ArrayList<>(collections)) {
+            collection.setOwner(owner);
+            visitor.visit(collection);
+            traverseActions(collection, visitor);
+        }
+    }
+
+    private BSElement.Visitor asBsVisitor(final Grid.Visitor visitor) {
+        return visitor instanceof BSElement.Visitor bsGridVisistor
+            ? bsGridVisistor
+            : new BSElement.Visitor() {
+                @Override public void visit(final DomainObjectLayoutData 
domainObjectLayoutData) {
+                    visitor.visit(domainObjectLayoutData);
+                }
+                @Override public void visit(final ActionLayoutData 
actionLayoutData) {
+                    visitor.visit(actionLayoutData);
+                }
+                @Override public void visit(final PropertyLayoutData 
propertyLayoutData) {
+                    visitor.visit(propertyLayoutData);
+                }
+                @Override public void visit(final CollectionLayoutData 
collectionLayoutData) {
+                    visitor.visit(collectionLayoutData);
+                }
+                @Override public void visit(final FieldSet fieldSet) {
+                    visitor.visit(fieldSet);
+                }
+            };
+    }
+
+}
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/HasCssId.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/HasElementId.java
similarity index 97%
rename from 
api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/HasCssId.java
rename to 
api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/HasElementId.java
index ce5d08d3a42..ac8bcc863a3 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/HasCssId.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/HasElementId.java
@@ -21,7 +21,7 @@
 /**
  * @since 2.0 {@index}
  */
-public interface HasCssId {
+public interface HasElementId {
 
     /**
      * As per &lt;div id=&quot;...&quot;&gt;...&lt;/div&gt; : must be unique 
across entire page.
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/WithinGrid.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/WithinGrid.java
deleted file mode 100644
index 40f8611acaf..00000000000
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/WithinGrid.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.causeway.applib.layout.grid.bootstrap;
-
-import org.apache.causeway.applib.annotation.Programmatic;
-
-/**
- * @since 1.x {@index}
- */
-public interface WithinGrid {
-
-    @Programmatic
-    BSGrid getGrid();
-}
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/layout/LayoutFacetUtil.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/layout/LayoutFacetUtil.java
index dfe32602d39..dd6e953eadb 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/layout/LayoutFacetUtil.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/layout/LayoutFacetUtil.java
@@ -367,7 +367,7 @@ public DomainObjectLayoutData 
createDomainObjectLayoutData() {
     }
 
     @RequiredArgsConstructor(staticName = "of")
-    public static class MetamodelToGridOverridingVisitor extends 
Grid.VisitorAdapter  {
+    public static class MetamodelToGridOverridingVisitor implements 
Grid.Visitor  {
 
         private final @NonNull ObjectSpecification objectSpec;
 
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridSystemServiceAbstract.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridSystemServiceAbstract.java
index a64db222c1a..8098fd12677 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridSystemServiceAbstract.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridSystemServiceAbstract.java
@@ -150,7 +150,7 @@ private void overwriteFacets(
                 : Facet.Precedence.HIGH; // non-fallback case: XML layout 
overrules layout from annotations
 
         final AtomicInteger propertySequence = new AtomicInteger(0);
-        fcGrid.visit(new Grid.VisitorAdapter() {
+        fcGrid.visit(new Grid.Visitor() {
             private int collectionSequence = 1;
 
             private int actionDomainObjectSequence = 1;
@@ -393,7 +393,7 @@ public void complete(final G grid, final Class<?> 
domainClass) {
     @Override
     public void minimal(final G grid, final Class<?> domainClass) {
         normalize(grid, domainClass);
-        grid.visit(new Grid.VisitorAdapter() {
+        grid.visit(new Grid.Visitor() {
             @Override
             public void visit(final ActionLayoutData actionLayoutData) {
                 
actionLayoutData.getOwner().getActions().remove(actionLayoutData);
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/CollapseIfOneTabProcessor.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/CollapseIfOneTabProcessor.java
index f7b0e4bb594..c6f5b449309 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/CollapseIfOneTabProcessor.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/CollapseIfOneTabProcessor.java
@@ -21,6 +21,7 @@
 import java.util.Optional;
 
 import org.apache.causeway.applib.layout.grid.bootstrap.BSCol;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSElement;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSGrid;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSTabGroup;
 
@@ -32,9 +33,9 @@
 record CollapseIfOneTabProcessor(BSGrid bsGrid) {
 
     public void run() {
-        bsGrid.visit(new BSGrid.VisitorAdapter() {
+        bsGrid.visit(new BSElement.Visitor() {
             @Override
-            public void visit(BSTabGroup bsTabGroup) {
+            public void visit(final BSTabGroup bsTabGroup) {
                 if(bsTabGroup.getTabs().size()!=1) return; // when has no tabs 
is also a no-op
 
                 var isCollapseIfOne = 
Optional.ofNullable(bsTabGroup.isCollapseIfOne())
@@ -48,7 +49,6 @@ public void visit(BSTabGroup bsTabGroup) {
                 bsTabGroup.getTabs().get(0).getRows()
                     .forEach(row->{
                         parent.getRows().add(row);
-                        row.setOwner(parent);
                     });
             }
         });
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/EmptyTabRemovalProcessor.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/EmptyTabRemovalProcessor.java
index 6655fa7620b..d33b51fe4ce 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/EmptyTabRemovalProcessor.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/EmptyTabRemovalProcessor.java
@@ -25,6 +25,7 @@
 import org.apache.causeway.applib.layout.component.CollectionLayoutData;
 import org.apache.causeway.applib.layout.component.DomainObjectLayoutData;
 import org.apache.causeway.applib.layout.component.PropertyLayoutData;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSElement;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSGrid;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSTab;
 
@@ -41,19 +42,19 @@ public void run() {
 
         var emptyTabs = new ArrayList<BSTab>();
 
-        bsGrid.visit(new BSGrid.VisitorAdapter() {
+        bsGrid.visit(new BSElement.Visitor() {
 
             final Stack<Flag> stack = new Stack<Flag>();
 
-            @Override public void visit(ActionLayoutData actionLayoutData) { 
keep(); }
-            @Override public void visit(DomainObjectLayoutData 
domainObjectLayoutData) { keep(); }
-            @Override public void visit(PropertyLayoutData propertyLayoutData) 
{ keep(); }
-            @Override public void visit(CollectionLayoutData 
collectionLayoutData) { keep(); }
+            @Override public void visit(final ActionLayoutData 
actionLayoutData) { keep(); }
+            @Override public void visit(final DomainObjectLayoutData 
domainObjectLayoutData) { keep(); }
+            @Override public void visit(final PropertyLayoutData 
propertyLayoutData) { keep(); }
+            @Override public void visit(final CollectionLayoutData 
collectionLayoutData) { keep(); }
 
-            @Override public void visit(BSTab bsTab) {
+            @Override public void visit(final BSTab bsTab) {
                 stack.push(new Flag());
             }
-            @Override public void postVisit(BSTab bsTab) {
+            @Override public void postVisit(final BSTab bsTab) {
                 var flag = stack.pop();
                 if(!flag.keep) {
                     // collecting into list, so we don't risk a 
ConcurrentModificationException,
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridConverterFromDto.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridConverterFromDto.java
new file mode 100644
index 00000000000..ad9b0ba04f1
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridConverterFromDto.java
@@ -0,0 +1,78 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.core.metamodel.services.grid.bootstrap;
+
+import org.apache.causeway.applib.layout.grid.bootstrap.BSGrid;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSGridDto;
+
+record GridConverterFromDto(BSGridDto dto) {
+
+    public BSGrid createGrid() {
+        var grid = new BSGrid();
+        dto.getRows().forEach(row->{
+            grid.getRows().add(row);
+        });
+        dto.getRows().clear();
+        return grid;
+    }
+
+
+//    private void getAllMemberById(final BSGridDto dto) {
+//        final LinkedHashMap<String, PropertyLayoutData> propertiesById = new 
LinkedHashMap<>();
+//        final LinkedHashMap<String, CollectionLayoutData> collectionsById = 
new LinkedHashMap<>();
+//        final LinkedHashMap<String, ActionLayoutData> actionsById = new 
LinkedHashMap<>();
+//
+//        new BSWalker(dto).visit(new Grid.Visitor() {
+//            @Override
+//            public void visit(final PropertyLayoutData propertyLayoutData) {
+//                propertiesById.put(propertyLayoutData.getId(), 
propertyLayoutData);
+//            }
+//            @Override
+//            public void visit(final CollectionLayoutData 
collectionLayoutData) {
+//                collectionsById.put(collectionLayoutData.getId(), 
collectionLayoutData);
+//            }
+//            @Override
+//            public void visit(final ActionLayoutData actionLayoutData) {
+//                actionsById.put(actionLayoutData.getId(), actionLayoutData);
+//            }
+//        });
+//    }
+//
+//    record DtoVisitor(
+//            LinkedHashMap<String, PropertyLayoutData> propertiesById,
+//            LinkedHashMap<String, CollectionLayoutData> collectionsById,
+//            LinkedHashMap<String, ActionLayoutData> actionsById) implements 
Grid.Visitor {
+//        DtoVisitor() {
+//            this(new LinkedHashMap<>(), new LinkedHashMap<>(), new 
LinkedHashMap<>());
+//        }
+//        @Override
+//        public void visit(final PropertyLayoutData propertyLayoutData) {
+//            propertiesById.put(propertyLayoutData.getId(), 
propertyLayoutData);
+//        }
+//        @Override
+//        public void visit(final CollectionLayoutData collectionLayoutData) {
+//            collectionsById.put(collectionLayoutData.getId(), 
collectionLayoutData);
+//        }
+//        @Override
+//        public void visit(final ActionLayoutData actionLayoutData) {
+//            actionsById.put(actionLayoutData.getId(), actionLayoutData);
+//        }
+//    }
+
+}
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSElement.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridConverterToDto.java
similarity index 57%
copy from 
api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSElement.java
copy to 
core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridConverterToDto.java
index 382dfa890f4..a6cb956b5ca 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSElement.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridConverterToDto.java
@@ -16,22 +16,25 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.applib.layout.grid.bootstrap;
+package org.apache.causeway.core.metamodel.services.grid.bootstrap;
 
-import java.io.Serializable;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSGrid;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSGridDto;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSRowOwner;
 
-/**
- * @since 1.x {@index}
- */
-public interface BSElement extends WithinGrid, Serializable {
+record GridConverterToDto(BSGrid grid) {
+
+    public BSGridDto createDto() {
+        var dto = new BSGridDto();
+        traversRows(grid, dto);
+        return dto;
+    }
 
-    /**
-     * Any additional CSS classes to render on the page element corresponding 
to this object,
-     * eg as per the <a 
href="http://getbootstrap.com/css/#grid-less";>Bootstrap mixins</a> or just for
-     * custom styling.
-     */
-    String getCssClass();
+    private void traversRows(final BSGrid grid, final BSRowOwner rowOwner) {
+        rowOwner.getRows().forEach(row->{
+            grid.getRows().add(row);
+        });
 
-    void setCssClass(final String cssClass);
+    }
 
 }
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridMarshallerServiceBootstrap.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridMarshallerServiceBootstrap.java
index 0f9ea4d6b2f..d2b6294c54c 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridMarshallerServiceBootstrap.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridMarshallerServiceBootstrap.java
@@ -19,6 +19,7 @@
 package org.apache.causeway.core.metamodel.services.grid.bootstrap;
 
 import java.util.EnumSet;
+import java.util.Map;
 import java.util.Objects;
 
 import jakarta.annotation.Priority;
@@ -26,24 +27,22 @@
 import jakarta.inject.Named;
 import jakarta.xml.bind.Marshaller;
 
+import org.jspecify.annotations.NonNull;
+
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSGrid;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSGridDto;
 import org.apache.causeway.applib.services.grid.GridMarshallerService;
 import org.apache.causeway.applib.services.jaxb.JaxbService;
 import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
 import org.apache.causeway.commons.functional.Try;
-import org.apache.causeway.commons.internal.collections._Maps;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.commons.io.JaxbUtils;
 import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel;
 
-import lombok.Getter;
-import org.jspecify.annotations.NonNull;
-import lombok.experimental.Accessors;
-
 /**
  * Default implementation of {@link GridMarshallerService} using DTOs based on
  * <a href="https://getbootstrap.com>Bootstrap</a> design system.
@@ -54,23 +53,21 @@
 @Named(CausewayModuleCoreMetamodel.NAMESPACE + 
".GridMarshallerServiceBootstrap")
 @Priority(PriorityPrecedence.MIDPOINT)
 @Qualifier("Default")
-//@Slf4j
-public class GridMarshallerServiceBootstrap
-implements GridMarshallerService<BSGrid> {
-
-    private final JaxbService jaxbService;
+public record GridMarshallerServiceBootstrap(
+        JaxbService jaxbService,
+        EnumSet<CommonMimeType> supportedFormats) implements 
GridMarshallerService<BSGrid> {
 
     @Inject
     public GridMarshallerServiceBootstrap(final JaxbService jaxbService) {
-        super();
-        this.jaxbService = jaxbService;
+        this(jaxbService, EnumSet.of(CommonMimeType.XML));
         // eagerly create a JAXBContext for this grid type (and cache it)
-        JaxbUtils.jaxbContextFor(BSGrid.class, true);
+        JaxbUtils.jaxbContextFor(BSGridDto.class, true);
     }
 
-    @Getter(onMethod_={@Override}) @Accessors(fluent = true)
-    private final EnumSet<CommonMimeType> supportedFormats =
-        EnumSet.of(CommonMimeType.XML);
+    @Override
+    public EnumSet<CommonMimeType> supportedFormats() {
+        return supportedFormats;
+    }
 
     @Override
     public Class<BSGrid> supportedClass() {
@@ -78,15 +75,15 @@ public Class<BSGrid> supportedClass() {
     }
 
     @Override
-    public String marshal(final @NonNull BSGrid grid, final @NonNull 
CommonMimeType format) {
+    public String marshal(final @NonNull BSGrid bsGrid, final @NonNull 
CommonMimeType format) {
         throwIfFormatNotSupported(format);
         switch(format) {
         case XML:{
-            return jaxbService.toXml(grid,
-                    _Maps.unmodifiable(
-                            Marshaller.JAXB_SCHEMA_LOCATION,
-                            
Objects.requireNonNull(grid.getTnsAndSchemaLocation())
-                            ));
+            var dto = new GridConverterToDto(bsGrid).createDto();
+            return jaxbService.toXml(dto,
+                Map.of(
+                    Marshaller.JAXB_SCHEMA_LOCATION,
+                    Objects.requireNonNull(bsGrid.getTnsAndSchemaLocation())));
         }
         default:
             throw _Exceptions.unsupportedOperation("supported format %s is not 
implemented", format.name());
@@ -98,7 +95,8 @@ public Try<BSGrid> unmarshal(final String content, final 
@NonNull CommonMimeType
         throwIfFormatNotSupported(format);
         switch(format) {
         case XML:{
-            return Try.call(()->jaxbService.fromXml(BSGrid.class, content));
+            return Try.call(()->jaxbService.fromXml(BSGridDto.class, content))
+                .mapSuccessWhenPresent(dto->new 
GridConverterFromDto(dto).createGrid());
         }
         default:
             throw _Exceptions.unsupportedOperation("supported format %s is not 
implemented", format.name());
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/_GridModel.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridModel.java
similarity index 97%
rename from 
core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/_GridModel.java
rename to 
core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridModel.java
index 71f0b099590..5f0aadbb5cc 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/_GridModel.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridModel.java
@@ -26,6 +26,7 @@
 
 import org.apache.causeway.applib.layout.component.FieldSet;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSCol;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSElement;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSGrid;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSRow;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSTabGroup;
@@ -42,7 +43,7 @@
  * @since 2.0
  */
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
-final class _GridModel {
+final class GridModel {
         private final LinkedHashSet<String> allIds = _Sets.newLinkedHashSet();
         private final LinkedHashMap<String, BSRow> rows = 
_Maps.newLinkedHashMap();
         private final LinkedHashMap<String, BSCol> cols = 
_Maps.newLinkedHashMap();
@@ -77,19 +78,6 @@ Either<BSCol, BSTabGroup> nodeForUnreferencedCollections() {
 
         private boolean gridErrorsDetected = false;
 
-        public boolean contains(final String id) {
-            return allIds.contains(id);
-        }
-
-        public Collection<FieldSet> fieldSets() {
-            return fieldSets.values();
-        }
-        public boolean containsFieldSetId(final String id) {
-            return fieldSets.containsKey(id);
-        }
-        public FieldSet getFieldSet(final String id) {
-            return fieldSets.get(id);
-        }
 
         /**
          * find all row and col ids<br>
@@ -99,11 +87,11 @@ public FieldSet getFieldSet(final String id) {
          * @param bsGrid
          * @return empty if not valid
          */
-        public static Optional<_GridModel> createFrom(final BSGrid bsGrid) {
+        public static Optional<GridModel> createFrom(final BSGrid bsGrid) {
 
-            var gridModel = new _GridModel();
+            var gridModel = new GridModel();
 
-            bsGrid.visit(new BSGrid.VisitorAdapter(){
+            bsGrid.visit(new BSElement.Visitor() {
                 @Override
                 public void visit(final BSRow bsRow) {
                     final String id = bsRow.getId();
@@ -154,7 +142,7 @@ public void visit(final FieldSet fieldSet) {
                 return Optional.empty();
             }
 
-            bsGrid.visit(new BSGrid.VisitorAdapter(){
+            bsGrid.visit(new BSElement.Visitor(){
 
                 @Override
                 public void visit(final BSCol bsCol) {
@@ -234,6 +222,20 @@ public void visit(final BSTabGroup bsTabGroup) {
                 : Optional.empty();
         }
 
+        public boolean contains(final String id) {
+            return allIds.contains(id);
+        }
+
+        public Collection<FieldSet> fieldSets() {
+            return fieldSets.values();
+        }
+        public boolean containsFieldSetId(final String id) {
+            return fieldSets.containsKey(id);
+        }
+        public FieldSet getFieldSet(final String id) {
+            return fieldSets.get(id);
+        }
+
         private void putRow(final String id, final BSRow bsRow) {
             rows.put(id, bsRow);
             allIds.add(id);
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
index aafb34fd5ab..25fa8deae96 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
@@ -245,6 +245,12 @@ protected boolean validateAndNormalize(
         var bsGrid = (BSGrid) grid;
         var objectSpec = 
specLoaderProvider.get().specForTypeElseFail(domainClass);
 
+        var gridModelIfValid = GridModel.createFrom(bsGrid);
+        if(!gridModelIfValid.isPresent()) { // only present if valid
+            return false;
+        }
+        var gridModel = gridModelIfValid.get();
+
         var oneToOneAssociationById = 
ObjectMember.mapById(objectSpec.streamProperties(MixedIn.INCLUDED));
         var oneToManyAssociationById = 
ObjectMember.mapById(objectSpec.streamCollections(MixedIn.INCLUDED));
         var objectActionById = 
ObjectMember.mapById(objectSpec.streamRuntimeActions(MixedIn.INCLUDED));
@@ -253,12 +259,6 @@ protected boolean validateAndNormalize(
         var collectionLayoutDataById = bsGrid.getAllCollectionsById();
         var actionLayoutDataById = bsGrid.getAllActionsById();
 
-        var gridModelIfValid = _GridModel.createFrom(bsGrid);
-        if(!gridModelIfValid.isPresent()) { // only present if valid
-            return false;
-        }
-        var gridModel = gridModelIfValid.get();
-
         var layoutDataFactory = LayoutDataFactory.of(objectSpec);
 
         // * surplus ... those defined in the grid model but not available 
with the meta-model
@@ -581,13 +581,11 @@ private void addUnreferencedCollectionsTo(
             });
 
             final BSRow tabRow = new BSRow();
-            tabRow.setOwner(bsTab);
             bsTab.getRows().add(tabRow);
 
             final BSCol tabRowCol = new BSCol();
             tabRowCol.setSpan(12);
             tabRowCol.setSize(Size.MD);
-            tabRowCol.setOwner(tabRow);
             tabRow.getCols().add(tabRowCol);
 
             var collectionLayoutData = layoutFactory.apply(collectionId);
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/sitemap/SitemapServiceDefault.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/sitemap/SitemapServiceDefault.java
index 3aec0af1e3e..3fee7508d78 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/sitemap/SitemapServiceDefault.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/sitemap/SitemapServiceDefault.java
@@ -36,7 +36,6 @@
 import org.apache.causeway.applib.layout.component.ServiceActionLayoutData;
 import org.apache.causeway.applib.layout.grid.Grid;
 import org.apache.causeway.applib.layout.menubars.bootstrap.BSMenuBars;
-import org.apache.causeway.applib.services.grid.GridService;
 import org.apache.causeway.applib.services.menu.MenuBarsService;
 import org.apache.causeway.applib.services.sitemap.SitemapService;
 import org.apache.causeway.commons.internal.base._NullSafe;
@@ -63,7 +62,6 @@
 public class SitemapServiceDefault implements SitemapService {
 
     private final SpecificationLoader specificationLoader;
-    private final GridService gridService;
     private final MenuBarsService menuBarsService;
 
     @Override
@@ -122,7 +120,7 @@ public String toSitemapAdoc(final String title) {
                     var grid = 
specificationLoader.specForType(actionElementType.getCorrespondingClass())
                                 .flatMap(Facets::bootstrapGrid)
                                 .orElse(null);
-                    grid.visit(new Grid.VisitorAdapter() {
+                    grid.visit(new Grid.Visitor() {
                         @Override public void visit(final ActionLayoutData 
actionLayoutData) {
                             
actionElementType.getAction(actionLayoutData.getId(), 
ActionScope.PRODUCTION_ONLY)
                             .ifPresent(action->{
diff --git 
a/extensions/core/docgen/help/src/main/java/org/apache/causeway/extensions/docgen/help/topics/welcome/WelcomeHelpPage.java
 
b/extensions/core/docgen/help/src/main/java/org/apache/causeway/extensions/docgen/help/topics/welcome/WelcomeHelpPage.java
index 017bc2064e7..e5d3b2795a1 100644
--- 
a/extensions/core/docgen/help/src/main/java/org/apache/causeway/extensions/docgen/help/topics/welcome/WelcomeHelpPage.java
+++ 
b/extensions/core/docgen/help/src/main/java/org/apache/causeway/extensions/docgen/help/topics/welcome/WelcomeHelpPage.java
@@ -225,7 +225,7 @@ private StringBuffer documentationForObjectType(final 
ObjectSpecification object
             html.append("</ul>");
             html.append("<ul>");
             {
-                grid.visit(new Grid.VisitorAdapter() {
+                grid.visit(new Grid.Visitor() {
                     @Override
                     public void visit(final FieldSet fieldSet) {
                         if (_NullSafe.isEmpty(fieldSet.getProperties())) {
diff --git 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
index 52a54ca69c4..91130c3beb5 100644
--- 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
+++ 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
@@ -269,7 +269,7 @@ public static void addLinks(
             final String instanceId,
             final Grid grid) {
 
-        grid.visit(new Grid.VisitorAdapter() {
+        grid.visit(new Grid.Visitor() {
             @Override
             public void visit(final DomainObjectLayoutData 
domainObjectLayoutData) {
                 Link link = newLink(
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionlinks/serviceactions/MenuActionPanel.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionlinks/serviceactions/MenuActionPanel.java
index 156588b7532..d36b6680028 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionlinks/serviceactions/MenuActionPanel.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionlinks/serviceactions/MenuActionPanel.java
@@ -24,13 +24,11 @@
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.markup.html.panel.Fragment;
-
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.viewer.wicket.ui.panels.PanelBase;
 import org.apache.causeway.viewer.wicket.ui.util.Wkt;
 
-@SuppressWarnings("rawtypes")
-abstract class MenuActionPanel extends PanelBase {
+abstract class MenuActionPanel extends PanelBase<Object> {
 
     private static final long serialVersionUID = 1L;
 
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/col/Col.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/col/Col.java
index f1f181c6ac3..d03666c4faa 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/col/Col.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/col/Col.java
@@ -34,6 +34,7 @@
 import org.apache.causeway.applib.layout.grid.bootstrap.BSRow;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSTab;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSTabGroup;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSUtil;
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.commons.internal.collections._Lists;
@@ -156,7 +157,7 @@ private void buildGui() {
                 .filter(_NullSafe::isPresent)
                 .filter(bsTabGroup ->
                         _NullSafe.stream(bsTabGroup.getTabs())
-                                .anyMatch(BSTab.Predicates.notEmpty())
+                                .anyMatch(BSUtil::hasContent)
                 )
                 .collect(Collectors.toList());
 
@@ -168,7 +169,7 @@ private void buildGui() {
 
                 final String id = tabGroupRv.newChildId();
                 final List<BSTab> tabs = _NullSafe.stream(bsTabGroup.getTabs())
-                        .filter(BSTab.Predicates.notEmpty())
+                        .filter(BSUtil::hasContent)
                         .collect(Collectors.toList());
 
                 switch (tabs.size()) {
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/tabs/TabGroupPanel.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/tabs/TabGroupPanel.java
index bc992e0214c..14f3cfa0e19 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/tabs/TabGroupPanel.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/tabs/TabGroupPanel.java
@@ -30,6 +30,7 @@
 
 import org.apache.causeway.applib.layout.grid.bootstrap.BSTab;
 import org.apache.causeway.applib.layout.grid.bootstrap.BSTabGroup;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSUtil;
 import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.viewer.wicket.model.models.UiObjectWkt;
 import org.apache.causeway.viewer.wicket.model.util.ComponentHintKey;
@@ -54,7 +55,7 @@ private static List<ITab> tabsFor(final UiObjectWkt 
objectModel, final BSTabGrou
         final List<ITab> tabs = new ArrayList<>();
 
         final List<BSTab> tablist = _NullSafe.stream(bsTabGroup.getTabs())
-                .filter(BSTab.Predicates.notEmpty())
+                .filter(BSUtil::hasContent)
                 .collect(Collectors.toList());
 
         for (var bsTab : tablist) {


Reply via email to