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

ahuber pushed a commit to branch spring6
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/spring6 by this push:
     new 28d011e35b CAUSEWAY-3407: simplified menu model (trivially 
serializable)
28d011e35b is described below

commit 28d011e35b58f9e3a0e2d9cf894580610ab7355f
Author: Andi Huber <[email protected]>
AuthorDate: Fri Apr 7 09:43:49 2023 +0200

    CAUSEWAY-3407: simplified menu model (trivially serializable)
---
 .../interactions/managed/ManagedAction.java        |  2 +
 .../viewer/javafx/ui/main/MainViewFx.java          |  6 +-
 .../viewer/javafx/ui/main/MenuBuilderFx.java       | 19 +++---
 .../viewer/thymeflux/model/root/MenuBuilder.java   | 57 ----------------
 .../model/root/ThymefluxRootController.java        |  2 -
 .../test/src/main/resources/application.yml        | 28 ++++++++
 .../thymeflux/test/ThymefluxViewerTests.java       | 23 ++++++-
 .../viewer/src/main/resources/templates/root.html  | 18 ++---
 .../ui/pages/main/MainView_createHeader.java       |  9 +--
 .../vaadin/ui/pages/main/MenuBuilderVaa.java       | 25 +++----
 .../commons/applib/src/main/java/module-info.java  |  9 ++-
 .../applib/services/header/HeaderUiModel.java      |  6 +-
 .../commons/applib/services/menu/MenuUiModel.java  | 58 ----------------
 .../applib/services/menu/MenuUiService.java        |  7 +-
 .../commons/applib/services/menu/MenuVisitor.java  | 14 ++--
 .../applib/services/menu/model/MenuAction.java     | 52 +++++++++++++++
 .../{MenuVisitor.java => model/MenuDropdown.java}  | 15 ++---
 .../MenuDropdownBuilder.java}                      | 32 ++++++---
 .../{MenuUiService.java => model/MenuEntry.java}   | 21 +++---
 .../{MenuVisitor.java => model/MenuSpacer.java}    | 19 +++---
 .../applib/services/menu/model/NavBarSection.java  | 66 ++++++++++++++++++
 .../{MenuVisitor.java => model/NavbarUiModel.java} | 16 ++---
 .../services/src/main/java/module-info.java        | 12 ++--
 .../services/header/HeaderUiServiceDefault.java    |  5 +-
 .../services/menu/MenuUiServiceDefault.java        | 78 ++++++++++++----------
 .../commons/services/menu/_MenuItemBuilder.java    | 22 ++++--
 .../wicket/model/models/ServiceActionsModel.java   | 16 ++---
 .../entityactions/LinkAndLabelFactory.java         | 12 ++--
 .../serviceactions/ServiceActionUtil.java          | 39 ++++-------
 .../serviceactions/ServiceActionsPanelFactory.java |  8 +--
 .../serviceactions/TertiaryMenuPanelFactory.java   |  6 +-
 .../wicket/ui/components/header/HeaderPanel.java   | 15 +++--
 32 files changed, 400 insertions(+), 317 deletions(-)

diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedAction.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedAction.java
index e18a23f346..4ee38304ec 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedAction.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedAction.java
@@ -238,4 +238,6 @@ public final class ManagedAction extends ManagedMember {
     }
 
 
+
+
 }
diff --git 
a/incubator/viewers/javafx/ui/src/main/java/org/apache/causeway/incubator/viewer/javafx/ui/main/MainViewFx.java
 
b/incubator/viewers/javafx/ui/src/main/java/org/apache/causeway/incubator/viewer/javafx/ui/main/MainViewFx.java
index 9ea2c64a59..edaed5b32a 100644
--- 
a/incubator/viewers/javafx/ui/src/main/java/org/apache/causeway/incubator/viewer/javafx/ui/main/MainViewFx.java
+++ 
b/incubator/viewers/javafx/ui/src/main/java/org/apache/causeway/incubator/viewer/javafx/ui/main/MainViewFx.java
@@ -104,9 +104,9 @@ public class MainViewFx {
         val leftMenuBuilder = MenuBuilderFx.of(uiContext, menuBarLeft, 
uiActionHandler::handleActionLinkClicked);
         val rightMenuBuilder = MenuBuilderFx.of(uiContext, menuBarRight, 
uiActionHandler::handleActionLinkClicked);
 
-        header.getPrimary().buildMenuItems(metaModelContext, leftMenuBuilder);
-        header.getSecondary().buildMenuItems(metaModelContext, 
rightMenuBuilder);
-        header.getTertiary().buildMenuItems(metaModelContext, 
rightMenuBuilder);
+        header.getNavbar().primary().visitMenuItems(leftMenuBuilder);
+        header.getNavbar().secondary().visitMenuItems(rightMenuBuilder);
+        header.getNavbar().tertiary().visitMenuItems(rightMenuBuilder);
     }
 
     private void replaceContent(final Node node) {
diff --git 
a/incubator/viewers/javafx/ui/src/main/java/org/apache/causeway/incubator/viewer/javafx/ui/main/MenuBuilderFx.java
 
b/incubator/viewers/javafx/ui/src/main/java/org/apache/causeway/incubator/viewer/javafx/ui/main/MenuBuilderFx.java
index 7cb3862677..0e7053c44e 100644
--- 
a/incubator/viewers/javafx/ui/src/main/java/org/apache/causeway/incubator/viewer/javafx/ui/main/MenuBuilderFx.java
+++ 
b/incubator/viewers/javafx/ui/src/main/java/org/apache/causeway/incubator/viewer/javafx/ui/main/MenuBuilderFx.java
@@ -22,8 +22,9 @@ import java.util.function.Consumer;
 
 import org.apache.causeway.core.metamodel.interactions.managed.ManagedAction;
 import org.apache.causeway.incubator.viewer.javafx.model.context.UiContextFx;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuItemDto;
 import org.apache.causeway.viewer.commons.applib.services.menu.MenuVisitor;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.MenuAction;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.MenuDropdown;
 
 import javafx.scene.control.Menu;
 import javafx.scene.control.MenuBar;
@@ -44,18 +45,18 @@ public class MenuBuilderFx implements MenuVisitor {
     private Menu currentTopLevelMenu = null;
 
     @Override
-    public void addTopLevel(MenuItemDto menuDto) {
-        log.debug("top level menu {}", menuDto.getName());
+    public void onTopLevel(final MenuDropdown menuDto) {
+        log.debug("top level menu {}", menuDto.name());
 
         menuBar.getMenus()
-        .add(currentTopLevelMenu = new Menu(menuDto.getName()));
+        .add(currentTopLevelMenu = new Menu(menuDto.name()));
     }
 
     @Override
-    public void addSubMenu(MenuItemDto menuDto) {
-        val managedAction = menuDto.getManagedAction();
+    public void onMenuAction(final MenuAction menuDto) {
+        val managedAction = menuDto.managedAction();
 
-        log.debug("sub menu {}", menuDto.getName());
+        log.debug("sub menu {}", menuDto.name());
 
         val actionUiModel = 
uiContext.getActionUiModelFactory().newActionUiModel(uiContext, managedAction);
         val menuItem = actionUiModel.createMenuUiComponent();
@@ -64,13 +65,13 @@ public class MenuBuilderFx implements MenuVisitor {
     }
 
     @Override
-    public void addSectionSpacer() {
+    public void onSectionSpacer() {
         log.debug("menu spacer");
         currentTopLevelMenu.getItems().add(new SeparatorMenuItem());
     }
 
     @Override
-    public void addSectionLabel(String named) {
+    public void onSectionLabel(final String named) {
         log.debug("section label  {}", named);
         val menuItem = new MenuItem(named);
         currentTopLevelMenu.getItems().add(menuItem);
diff --git 
a/incubator/viewers/thymeflux/model/src/main/java/org/apache/causeway/viewer/thymeflux/model/root/MenuBuilder.java
 
b/incubator/viewers/thymeflux/model/src/main/java/org/apache/causeway/viewer/thymeflux/model/root/MenuBuilder.java
deleted file mode 100644
index 69e3ddcc36..0000000000
--- 
a/incubator/viewers/thymeflux/model/src/main/java/org/apache/causeway/viewer/thymeflux/model/root/MenuBuilder.java
+++ /dev/null
@@ -1,57 +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.viewer.thymeflux.model.root;
-
-import java.awt.MenuItem;
-
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuItemDto;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuVisitor;
-
-import lombok.RequiredArgsConstructor;
-import lombok.val;
-import lombok.extern.log4j.Log4j2;
-
-//TODO just a stub yet
-@RequiredArgsConstructor(staticName = "of")
-@Log4j2
-class MenuBuilder implements MenuVisitor {
-
-    @Override
-    public void addTopLevel(final MenuItemDto menuDto) {
-        log.debug("top level menu {}", menuDto.getName());
-    }
-
-    @Override
-    public void addSubMenu(final MenuItemDto menuDto) {
-        val managedAction = menuDto.getManagedAction();
-        log.debug("sub menu {}", menuDto.getName());
-    }
-
-    @Override
-    public void addSectionSpacer() {
-        log.debug("menu spacer");
-    }
-
-    @Override
-    public void addSectionLabel(final String named) {
-        log.debug("section label  {}", named);
-        val menuItem = new MenuItem(named);
-    }
-
-}
diff --git 
a/incubator/viewers/thymeflux/model/src/main/java/org/apache/causeway/viewer/thymeflux/model/root/ThymefluxRootController.java
 
b/incubator/viewers/thymeflux/model/src/main/java/org/apache/causeway/viewer/thymeflux/model/root/ThymefluxRootController.java
index 4cf53ddf38..2cb59f7b91 100644
--- 
a/incubator/viewers/thymeflux/model/src/main/java/org/apache/causeway/viewer/thymeflux/model/root/ThymefluxRootController.java
+++ 
b/incubator/viewers/thymeflux/model/src/main/java/org/apache/causeway/viewer/thymeflux/model/root/ThymefluxRootController.java
@@ -47,9 +47,7 @@ public class ThymefluxRootController {
         interactionService.run(interactionContextMockup, ()->{
 
             var headerUiModel = headerUiModelProvider.getHeader();
-
             model.addAttribute("headerUiModel", headerUiModel);
-
         });
 
         //TODO on error use error template instead
diff --git 
a/incubator/viewers/thymeflux/test/src/main/resources/application.yml 
b/incubator/viewers/thymeflux/test/src/main/resources/application.yml
index d89e5ba630..1afed3a6d7 100644
--- a/incubator/viewers/thymeflux/test/src/main/resources/application.yml
+++ b/incubator/viewers/thymeflux/test/src/main/resources/application.yml
@@ -91,6 +91,34 @@ causeway:
         - url:  https://causeway.apache.org
           image: images/gift_48.png
           name: Apache Causeway
+          
+  # schema auto creation etc. ...
+  persistence:
+    schema:
+       autoCreateSchemas: 
causewayExtSecman,causewayExtCommandLog,causewayExtExecutionLog,causewayExtExecutionOutbox,causewayExtSessionLog,causewayExtAuditTrail,demo
+
+  extensions:
+    secman:
+      seed:
+        admin:
+          user-name: "sven"
+          password: "pass"
+          role-name: "causeway-ext-secman-admin"
+          namespace-permissions:
+            sticky: "causeway"
+            additional: "demo"
+        regular-user:
+          role-name: "causeway-ext-secman-user"
+      permissionsEvaluationPolicy: ALLOW_BEATS_VETO
+
+  testing:
+    fixtures:
+      fixture-scripts-specification:
+        context-class: demoapp.dom._infra.fixtures.DemoFixtureScript
+        multiple-execution-strategy: execute_once_by_value
+        non-persisted-objects-strategy: ignore
+        recreate: demoapp.dom._infra.fixtures.DemoFixtureScript
+        run-script-default: demoapp.dom._infra.fixtures.DemoFixtureScript      
    
 
 server:
   http2:
diff --git 
a/incubator/viewers/thymeflux/test/src/test/java/org/apache/causeway/viewer/thymeflux/test/ThymefluxViewerTests.java
 
b/incubator/viewers/thymeflux/test/src/test/java/org/apache/causeway/viewer/thymeflux/test/ThymefluxViewerTests.java
index a819ec3587..b8bf73f4a1 100644
--- 
a/incubator/viewers/thymeflux/test/src/test/java/org/apache/causeway/viewer/thymeflux/test/ThymefluxViewerTests.java
+++ 
b/incubator/viewers/thymeflux/test/src/test/java/org/apache/causeway/viewer/thymeflux/test/ThymefluxViewerTests.java
@@ -26,8 +26,12 @@ import org.springframework.test.context.ActiveProfiles;
 import org.springframework.ui.Model;
 import org.springframework.validation.support.BindingAwareModelMap;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
+import org.apache.causeway.applib.annotation.DomainServiceLayout.MenuBar;
+import org.apache.causeway.viewer.commons.applib.services.header.HeaderUiModel;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.NavBarSection;
 import org.apache.causeway.viewer.thymeflux.model.root.ThymefluxRootController;
 import 
org.apache.causeway.viewer.thymeflux.viewer.CausewayModuleIncViewerThymefluxViewer;
 
@@ -55,8 +59,25 @@ class ThymefluxViewerTests {
         final Model model = new BindingAwareModelMap();
         rootController.root(model);
 
-        var headerUiModel = model.getAttribute("headerUiModel");
+        var headerUiModel = (HeaderUiModel)model.getAttribute("headerUiModel");
         assertNotNull(headerUiModel);
+
+        var primary   = (NavBarSection)headerUiModel.getNavbar().primary();
+        var secondary = (NavBarSection)headerUiModel.getNavbar().secondary();
+        var tertiary  = (NavBarSection)headerUiModel.getNavbar().tertiary();
+
+        assertEquals(MenuBar.PRIMARY,   primary.menuBarSelect());
+        assertEquals(MenuBar.SECONDARY, secondary.menuBarSelect());
+        assertEquals(MenuBar.TERTIARY,  tertiary.menuBarSelect());
+
+        primary.topLevelEntries().forEach(top->System.err.printf("prim: %s%n", 
top.name()));
+        secondary.topLevelEntries().forEach(top->System.err.printf("sec: 
%s%n", top.name()));
+        tertiary.topLevelEntries().forEach(top->System.err.printf("tert: 
%s%n", top.name()));
+
+        primary.topLevelEntries().forEach(top->System.err.printf("prim: %s%n", 
top));
+        secondary.topLevelEntries().forEach(top->System.err.printf("sec: 
%s%n", top));
+        tertiary.topLevelEntries().forEach(top->System.err.printf("tert: 
%s%n", top));
+
     }
 
 
diff --git 
a/incubator/viewers/thymeflux/viewer/src/main/resources/templates/root.html 
b/incubator/viewers/thymeflux/viewer/src/main/resources/templates/root.html
index 0cfb934923..4e00613028 100644
--- a/incubator/viewers/thymeflux/viewer/src/main/resources/templates/root.html
+++ b/incubator/viewers/thymeflux/viewer/src/main/resources/templates/root.html
@@ -87,17 +87,17 @@
                 <td>[[${headerUiModel.branding.logoHref}]]</td>
             </tr>
             
-            <tr class="result" th:each="menuModel : ${headerUiModel.primary}">
-                <td>[[${menuModel.menuBarSelect}]]</td>
-                <td>[[${menuModel.menuContributingServiceIds}]]</td>
+            <tr class="result" th:each="topLevelEntry : 
${primary.topLevelEntries()}">
+                <td>[[${topLevelEntry.name}]]</td>
+                <td>[[${topLevelEntry.subEntries}]]</td>
             </tr>
-            <tr class="result" th:each="menuModel : 
${headerUiModel.secondary}">
-                <td>[[${menuModel.menuBarSelect}]]</td>
-                <td>[[${menuModel.menuContributingServiceIds}]]</td>
+            <tr class="result" th:each="topLevelEntry : 
${secondary.topLevelEntries()}">
+                <td>[[${topLevelEntry.name}]]</td>
+                <td>[[${topLevelEntry.subEntries}]]</td>
             </tr>
-            <tr class="result" th:each="menuModel : ${headerUiModel.tertiary}">
-                <td>[[${menuModel.menuBarSelect}]]</td>
-                <td>[[${menuModel.menuContributingServiceIds}]]</td>
+            <tr class="result" th:each="topLevelEntry : 
${tertiary.topLevelEntries()}">
+                <td>[[${topLevelEntry.name}]]</td>
+                <td>[[${topLevelEntry.subEntries}]]</td>
             </tr>
             
             <tr class="result">
diff --git 
a/incubator/viewers/vaadin/ui/src/main/java/org/apache/causeway/incubator/viewer/vaadin/ui/pages/main/MainView_createHeader.java
 
b/incubator/viewers/vaadin/ui/src/main/java/org/apache/causeway/incubator/viewer/vaadin/ui/pages/main/MainView_createHeader.java
index 0e95b8e915..3cc271493a 100644
--- 
a/incubator/viewers/vaadin/ui/src/main/java/org/apache/causeway/incubator/viewer/vaadin/ui/pages/main/MainView_createHeader.java
+++ 
b/incubator/viewers/vaadin/ui/src/main/java/org/apache/causeway/incubator/viewer/vaadin/ui/pages/main/MainView_createHeader.java
@@ -34,7 +34,6 @@ import 
org.apache.causeway.core.metamodel.interactions.managed.ManagedAction;
 import org.apache.causeway.incubator.viewer.vaadin.model.util.Vaa;
 import 
org.apache.causeway.viewer.commons.applib.services.branding.BrandingUiModel;
 import org.apache.causeway.viewer.commons.applib.services.header.HeaderUiModel;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuUiService;
 
 import lombok.val;
 
@@ -73,11 +72,9 @@ final class MainView_createHeader {
         val leftMenuBuilder = MenuBuilderVaa.of(commonContext, 
menuActionEventHandler, leftMenuBar);
         val rightMenuBuilder = MenuBuilderVaa.of(commonContext, 
menuActionEventHandler, rightMenuBar);
 
-        val menuUiModelProvider = 
commonContext.lookupServiceElseFail(MenuUiService.class);
-
-        headerUiModel.getPrimary().buildMenuItems(menuUiModelProvider, 
leftMenuBuilder);
-        headerUiModel.getSecondary().buildMenuItems(menuUiModelProvider, 
rightMenuBuilder);
-        headerUiModel.getTertiary().buildMenuItems(menuUiModelProvider, 
rightMenuBuilder);
+        headerUiModel.getNavbar().primary().visitMenuItems(leftMenuBuilder);
+        headerUiModel.getNavbar().secondary().visitMenuItems(rightMenuBuilder);
+        headerUiModel.getNavbar().tertiary().visitMenuItems(rightMenuBuilder);
 
         return menuBarContainer;
 
diff --git 
a/incubator/viewers/vaadin/ui/src/main/java/org/apache/causeway/incubator/viewer/vaadin/ui/pages/main/MenuBuilderVaa.java
 
b/incubator/viewers/vaadin/ui/src/main/java/org/apache/causeway/incubator/viewer/vaadin/ui/pages/main/MenuBuilderVaa.java
index 1bc5352401..ac4910355b 100644
--- 
a/incubator/viewers/vaadin/ui/src/main/java/org/apache/causeway/incubator/viewer/vaadin/ui/pages/main/MenuBuilderVaa.java
+++ 
b/incubator/viewers/vaadin/ui/src/main/java/org/apache/causeway/incubator/viewer/vaadin/ui/pages/main/MenuBuilderVaa.java
@@ -29,8 +29,9 @@ import 
org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.interactions.managed.ManagedAction;
 import 
org.apache.causeway.incubator.viewer.vaadin.model.action.ActionUiModelFactoryVaa;
 import org.apache.causeway.incubator.viewer.vaadin.model.decorator.Decorators;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuItemDto;
 import org.apache.causeway.viewer.commons.applib.services.menu.MenuVisitor;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.MenuAction;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.MenuDropdown;
 
 import lombok.RequiredArgsConstructor;
 import lombok.val;
@@ -47,20 +48,20 @@ class MenuBuilderVaa implements MenuVisitor {
     private ActionUiModelFactoryVaa actionUiModelFactory = new 
ActionUiModelFactoryVaa();
 
     @Override
-    public void addTopLevel(MenuItemDto menuDto) {
+    public void onTopLevel(final MenuDropdown menuDto) {
 
-        if(menuDto.isTertiaryRoot()) {
-            currentTopLevelMenu = menuBar.addItem(Decorators.getUser()
-                    .decorateWithAvatar(new Label(), commonContext));
-        } else {
+//        if(menuDto.isTertiaryRoot()) {
+//            currentTopLevelMenu = menuBar.addItem(Decorators.getUser()
+//                    .decorateWithAvatar(new Label(), commonContext));
+//        } else {
             currentTopLevelMenu = menuBar.addItem(Decorators.getMenu()
-                    .decorateTopLevel(new Label(menuDto.getName())));
-        }
+                    .decorateTopLevel(new Label(menuDto.name())));
+//        }
     }
 
     @Override
-    public void addSubMenu(MenuItemDto menu) {
-        val managedAction = menu.getManagedAction();
+    public void onMenuAction(final MenuAction menuAction) {
+        val managedAction = menuAction.managedAction();
 
         val actionUiModel = 
actionUiModelFactory.newActionUiModel(managedAction);
         currentTopLevelMenu.getSubMenu()
@@ -68,7 +69,7 @@ class MenuBuilderVaa implements MenuVisitor {
     }
 
     @Override
-    public void addSectionSpacer() {
+    public void onSectionSpacer() {
         val sectionSpacer = new Hr();
         val menuItem = currentTopLevelMenu.getSubMenu().addItem(sectionSpacer);
         menuItem.setEnabled(false);
@@ -77,7 +78,7 @@ class MenuBuilderVaa implements MenuVisitor {
     }
 
     @Override
-    public void addSectionLabel(String named) {
+    public void onSectionLabel(final String named) {
         val sectionLabel = new Label(named);
         sectionLabel.addClassName("section-label");
         val menuItem = currentTopLevelMenu.getSubMenu().addItem(sectionLabel);
diff --git a/viewers/commons/applib/src/main/java/module-info.java 
b/viewers/commons/applib/src/main/java/module-info.java
index 55d4d4b33a..c0c5de8169 100644
--- a/viewers/commons/applib/src/main/java/module-info.java
+++ b/viewers/commons/applib/src/main/java/module-info.java
@@ -21,13 +21,16 @@ module org.apache.causeway.viewer.commons.applib {
     exports org.apache.causeway.viewer.commons.applib.services.header;
     exports org.apache.causeway.viewer.commons.applib.services.branding;
     exports org.apache.causeway.viewer.commons.applib.services.menu;
+    exports org.apache.causeway.viewer.commons.applib.services.menu.model;
     exports org.apache.causeway.viewer.commons.applib;
     exports org.apache.causeway.viewer.commons.applib.mixins;
 
+    requires static lombok;
+
+    requires transitive org.apache.causeway.applib;
+    requires transitive org.apache.causeway.commons;
+
     requires jakarta.inject;
-    requires lombok;
-    requires org.apache.causeway.applib;
-    requires org.apache.causeway.commons;
     requires org.apache.causeway.core.config;
     requires org.apache.causeway.core.metamodel;
     requires spring.context;
diff --git 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/header/HeaderUiModel.java
 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/header/HeaderUiModel.java
index 7269a03347..06b037d2a1 100644
--- 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/header/HeaderUiModel.java
+++ 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/header/HeaderUiModel.java
@@ -19,7 +19,7 @@
 package org.apache.causeway.viewer.commons.applib.services.header;
 
 import 
org.apache.causeway.viewer.commons.applib.services.branding.BrandingUiModel;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuUiModel;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.NavbarUiModel;
 import 
org.apache.causeway.viewer.commons.applib.services.userprof.UserProfileUiModel;
 
 import lombok.AllArgsConstructor;
@@ -31,8 +31,6 @@ public class HeaderUiModel {
 
     private final BrandingUiModel branding;
     private final UserProfileUiModel userProfile;
-    private final MenuUiModel primary;
-    private final MenuUiModel secondary;
-    private final MenuUiModel tertiary;
+    private final NavbarUiModel navbar;
 
 }
diff --git 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiModel.java
 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiModel.java
deleted file mode 100644
index 18cf3ae5bb..0000000000
--- 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiModel.java
+++ /dev/null
@@ -1,58 +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.viewer.commons.applib.services.menu;
-
-import java.io.Serializable;
-import java.util.List;
-import java.util.Locale;
-
-import org.apache.causeway.applib.annotation.DomainServiceLayout;
-import org.apache.causeway.core.metamodel.context.MetaModelContext;
-
-import lombok.Getter;
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-
-@Getter
-@RequiredArgsConstructor(staticName = "of")
-//@Log4j2
-public class MenuUiModel implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    @NonNull private final DomainServiceLayout.MenuBar menuBarSelect;
-    @NonNull private final List<String> menuContributingServiceIds;
-
-    public String getCssClass() {
-        return menuBarSelect.name().toLowerCase(Locale.ENGLISH);
-    }
-
-    public void buildMenuItems(
-            final MetaModelContext mmc,
-            final MenuVisitor menuBuilder) {
-        
buildMenuItems(mmc.getServiceRegistry().lookupServiceElseFail(MenuUiService.class),
 menuBuilder);
-    }
-
-    public void buildMenuItems(
-            final MenuUiService menuUiService,
-            final MenuVisitor menuBuilder) {
-        menuUiService.buildMenuItems(this, menuBuilder);
-    }
-
-}
diff --git 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiService.java
 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiService.java
index fb87b1f7d9..8579198cca 100644
--- 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiService.java
+++ 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiService.java
@@ -18,14 +18,13 @@
  */
 package org.apache.causeway.viewer.commons.applib.services.menu;
 
-import org.apache.causeway.applib.annotation.DomainServiceLayout;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.NavbarUiModel;
 
 /**
- * @since 2.0 {@index}}
+ * @since 2.0 {@index}
  */
 public interface MenuUiService {
 
-    MenuUiModel getMenu(DomainServiceLayout.MenuBar menuBarSelect);
-    void buildMenuItems(MenuUiModel menuUiModel, MenuVisitor menuBuilder);
+    NavbarUiModel getMenu();
 
 }
diff --git 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
index 22b3d6bb05..a26d179f58 100644
--- 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
+++ 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
@@ -18,15 +18,21 @@
  */
 package org.apache.causeway.viewer.commons.applib.services.menu;
 
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.MenuAction;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.MenuDropdown;
+
+/**
+ * Depth first visitor of the nav-bar model.
+ */
 public interface MenuVisitor {
 
-    void addTopLevel(MenuItemDto menuDto);
-    void addSectionSpacer();
-    void addSubMenu(MenuItemDto menuDto);
+    void onTopLevel(MenuDropdown menuDropdown);
+    void onMenuAction(MenuAction menuAction);
 
+    void onSectionSpacer();
     /**
      * @param named - not null and not empty
      */
-    void addSectionLabel(String named);
+    void onSectionLabel(String named);
 
 }
diff --git 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuAction.java
 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuAction.java
new file mode 100644
index 0000000000..e691d68ecf
--- /dev/null
+++ 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuAction.java
@@ -0,0 +1,52 @@
+/*
+ *  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.viewer.commons.applib.services.menu.model;
+
+import org.springframework.lang.Nullable;
+
+import org.apache.causeway.applib.Identifier;
+import org.apache.causeway.applib.services.bookmark.Bookmark;
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
+import org.apache.causeway.core.metamodel.interactions.managed.ManagedAction;
+
+import lombok.NonNull;
+
+public record MenuAction (
+        @NonNull Bookmark serviceBookmark,
+        @NonNull Identifier actionId,
+        @NonNull String name,
+        @Nullable String cssClassFa
+        ) implements MenuEntry {
+
+
+    public static MenuAction of(final @NonNull ManagedAction managedAction) {
+        // TODO missing cssClass
+        return new MenuAction(
+                managedAction.getOwner().getBookmark().orElseThrow(),
+                managedAction.getIdentifier(),
+                managedAction.getFriendlyName(),
+                null);
+    }
+
+    @Deprecated
+    public ManagedAction managedAction(){
+        throw _Exceptions.notImplemented();
+    }
+
+}
diff --git 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuDropdown.java
similarity index 72%
copy from 
viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
copy to 
viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuDropdown.java
index 22b3d6bb05..252a103c23 100644
--- 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
+++ 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuDropdown.java
@@ -16,17 +16,14 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.viewer.commons.applib.services.menu;
+package org.apache.causeway.viewer.commons.applib.services.menu.model;
 
-public interface MenuVisitor {
+import org.apache.causeway.commons.collections.Can;
 
-    void addTopLevel(MenuItemDto menuDto);
-    void addSectionSpacer();
-    void addSubMenu(MenuItemDto menuDto);
+import lombok.NonNull;
 
-    /**
-     * @param named - not null and not empty
-     */
-    void addSectionLabel(String named);
+public record MenuDropdown (
+        @NonNull String name,
+        @NonNull Can<MenuEntry> subEntries) implements MenuEntry {
 
 }
diff --git 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiService.java
 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuDropdownBuilder.java
similarity index 51%
copy from 
viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiService.java
copy to 
viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuDropdownBuilder.java
index fb87b1f7d9..cec5e4847f 100644
--- 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiService.java
+++ 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuDropdownBuilder.java
@@ -16,16 +16,32 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.viewer.commons.applib.services.menu;
+package org.apache.causeway.viewer.commons.applib.services.menu.model;
 
-import org.apache.causeway.applib.annotation.DomainServiceLayout;
+import java.util.List;
 
-/**
- * @since 2.0 {@index}}
- */
-public interface MenuUiService {
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.core.metamodel.interactions.managed.ManagedAction;
+
+import lombok.NonNull;
+
+public record MenuDropdownBuilder (
+        @NonNull String name,
+        @NonNull List<MenuEntry> subEntries) {
+
+    public void addSectionSpacer() {
+        subEntries().add(MenuSpacer.empty());
+    }
+
+    public void addSectionSpacer(final @NonNull String label) {
+        subEntries().add(new MenuSpacer(label));
+    }
 
-    MenuUiModel getMenu(DomainServiceLayout.MenuBar menuBarSelect);
-    void buildMenuItems(MenuUiModel menuUiModel, MenuVisitor menuBuilder);
+    public void addAction(final ManagedAction action) {
+        subEntries().add(MenuAction.of(action));
+    }
 
+    public MenuDropdown build() {
+        return new MenuDropdown(name, Can.ofCollection(subEntries));
+    }
 }
diff --git 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiService.java
 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuEntry.java
similarity index 65%
copy from 
viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiService.java
copy to 
viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuEntry.java
index fb87b1f7d9..2d72d6f771 100644
--- 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuUiService.java
+++ 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuEntry.java
@@ -16,16 +16,21 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.viewer.commons.applib.services.menu;
+package org.apache.causeway.viewer.commons.applib.services.menu.model;
 
-import org.apache.causeway.applib.annotation.DomainServiceLayout;
+import java.io.Serializable;
+import java.util.Optional;
 
-/**
- * @since 2.0 {@index}}
- */
-public interface MenuUiService {
+import org.apache.causeway.commons.internal.base._Casts;
+
+public interface MenuEntry extends Serializable {
+
+    default Optional<MenuAction> asAction() {
+        return _Casts.castTo(MenuAction.class, this);
+    }
 
-    MenuUiModel getMenu(DomainServiceLayout.MenuBar menuBarSelect);
-    void buildMenuItems(MenuUiModel menuUiModel, MenuVisitor menuBuilder);
+    default Optional<MenuSpacer> asSpacer() {
+        return _Casts.castTo(MenuSpacer.class, this);
+    }
 
 }
diff --git 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuSpacer.java
similarity index 72%
copy from 
viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
copy to 
viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuSpacer.java
index 22b3d6bb05..e8ed5ccbbd 100644
--- 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
+++ 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/MenuSpacer.java
@@ -16,17 +16,18 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.viewer.commons.applib.services.menu;
+package org.apache.causeway.viewer.commons.applib.services.menu.model;
 
-public interface MenuVisitor {
+import lombok.NonNull;
 
-    void addTopLevel(MenuItemDto menuDto);
-    void addSectionSpacer();
-    void addSubMenu(MenuItemDto menuDto);
+public record MenuSpacer(@NonNull String label) implements MenuEntry {
 
-    /**
-     * @param named - not null and not empty
-     */
-    void addSectionLabel(String named);
+    public static MenuSpacer empty() {
+        return new MenuSpacer("");
+    }
+
+    public boolean isEmpty() {
+        return label.length() == 0;
+    }
 
 }
diff --git 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/NavBarSection.java
 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/NavBarSection.java
new file mode 100644
index 0000000000..aa43a2c830
--- /dev/null
+++ 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/NavBarSection.java
@@ -0,0 +1,66 @@
+/*
+ *  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.viewer.commons.applib.services.menu.model;
+
+import java.util.Locale;
+
+import org.springframework.lang.Nullable;
+
+import org.apache.causeway.applib.annotation.DomainServiceLayout;
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.viewer.commons.applib.services.menu.MenuVisitor;
+
+import lombok.val;
+
+public record NavBarSection(
+        DomainServiceLayout.MenuBar menuBarSelect,
+        Can<MenuDropdown> topLevelEntries) {
+
+    public String cssClass() {
+        return menuBarSelect.name().toLowerCase(Locale.ENGLISH);
+    }
+
+    /**
+     * Depth-first visit of the model.
+     * @param menuVisitor
+     */
+    public void visitMenuItems(final @Nullable MenuVisitor menuVisitor) {
+        if(menuVisitor==null) return;
+
+        topLevelEntries.forEach(topLevel->{
+            menuVisitor.onTopLevel(topLevel);
+            topLevel.subEntries().forEach(subEntry->{
+                val asAction = subEntry.asAction();
+                asAction.ifPresentOrElse(menuVisitor::onMenuAction, ()->{
+                    val asSpacer = subEntry.asSpacer();
+                    asSpacer.ifPresent(spacer->{
+                        if(spacer.isEmpty()) {
+                            menuVisitor.onSectionSpacer();
+                        } else {
+                            menuVisitor.onSectionLabel(spacer.label());
+                        }
+                    });
+                });
+
+            });
+        });
+
+    }
+
+}
diff --git 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/NavbarUiModel.java
similarity index 72%
copy from 
viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
copy to 
viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/NavbarUiModel.java
index 22b3d6bb05..9c45633373 100644
--- 
a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/MenuVisitor.java
+++ 
b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/services/menu/model/NavbarUiModel.java
@@ -16,17 +16,13 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.viewer.commons.applib.services.menu;
+package org.apache.causeway.viewer.commons.applib.services.menu.model;
 
-public interface MenuVisitor {
+import lombok.NonNull;
 
-    void addTopLevel(MenuItemDto menuDto);
-    void addSectionSpacer();
-    void addSubMenu(MenuItemDto menuDto);
-
-    /**
-     * @param named - not null and not empty
-     */
-    void addSectionLabel(String named);
+public record NavbarUiModel(
+        @NonNull NavBarSection primary,
+        @NonNull NavBarSection secondary,
+        @NonNull NavBarSection tertiary) {
 
 }
diff --git a/viewers/commons/services/src/main/java/module-info.java 
b/viewers/commons/services/src/main/java/module-info.java
index 51f4929a98..9d81d11495 100644
--- a/viewers/commons/services/src/main/java/module-info.java
+++ b/viewers/commons/services/src/main/java/module-info.java
@@ -23,14 +23,18 @@ module org.apache.causeway.viewer.commons.services {
     exports org.apache.causeway.viewer.commons.services.menu;
     exports org.apache.causeway.viewer.commons.services;
 
+    requires static lombok;
+
     requires jakarta.annotation;
     requires jakarta.inject;
-    requires lombok;
-    requires org.apache.causeway.applib;
-    requires org.apache.causeway.commons;
+
+    requires transitive org.apache.causeway.applib;
+    requires transitive org.apache.causeway.commons;
+    requires transitive org.apache.causeway.viewer.commons.applib;
+
     requires org.apache.causeway.core.config;
     requires org.apache.causeway.core.metamodel;
-    requires org.apache.causeway.viewer.commons.applib;
+
     requires org.apache.logging.log4j;
     requires spring.beans;
     requires spring.context;
diff --git 
a/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/header/HeaderUiServiceDefault.java
 
b/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/header/HeaderUiServiceDefault.java
index 6a86374843..5cd1b72255 100644
--- 
a/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/header/HeaderUiServiceDefault.java
+++ 
b/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/header/HeaderUiServiceDefault.java
@@ -25,7 +25,6 @@ import jakarta.inject.Named;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
-import org.apache.causeway.applib.annotation.DomainServiceLayout.MenuBar;
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import 
org.apache.causeway.viewer.commons.applib.services.branding.BrandingUiService;
 import org.apache.causeway.viewer.commons.applib.services.header.HeaderUiModel;
@@ -53,9 +52,7 @@ implements HeaderUiService {
         return HeaderUiModel.of(
                 brandingUiService.getHeaderBranding(),
                 userProfileUiService.userProfile(),
-                menuUiService.getMenu(MenuBar.PRIMARY),
-                menuUiService.getMenu(MenuBar.SECONDARY),
-                menuUiService.getMenu(MenuBar.TERTIARY));
+                menuUiService.getMenu());
     }
 
 }
diff --git 
a/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/menu/MenuUiServiceDefault.java
 
b/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/menu/MenuUiServiceDefault.java
index 3309edebfd..1e14541239 100644
--- 
a/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/menu/MenuUiServiceDefault.java
+++ 
b/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/menu/MenuUiServiceDefault.java
@@ -18,9 +18,7 @@
  */
 package org.apache.causeway.viewer.commons.services.menu;
 
-import java.util.List;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
+import java.util.ArrayList;
 
 import jakarta.annotation.Priority;
 import jakarta.inject.Inject;
@@ -29,17 +27,17 @@ import jakarta.inject.Named;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
-import org.apache.causeway.applib.annotation.DomainServiceLayout;
+import org.apache.causeway.applib.annotation.DomainServiceLayout.MenuBar;
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.applib.layout.menubars.bootstrap.BSMenuBar;
 import org.apache.causeway.applib.services.menu.MenuBarsService;
+import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
-import org.apache.causeway.core.metamodel.object.ManagedObject;
-import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
-import org.apache.causeway.core.metamodel.util.Facets;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuUiModel;
+import org.apache.causeway.viewer.commons.applib.services.menu.MenuItemDto;
 import org.apache.causeway.viewer.commons.applib.services.menu.MenuUiService;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuVisitor;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.MenuDropdownBuilder;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.NavBarSection;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.NavbarUiModel;
 import 
org.apache.causeway.viewer.commons.services.CausewayModuleViewerCommonsServices;
 
 import lombok.RequiredArgsConstructor;
@@ -57,41 +55,51 @@ implements MenuUiService {
     private final MenuBarsService menuBarsService;
 
     @Override
-    public MenuUiModel getMenu(final DomainServiceLayout.MenuBar 
menuBarSelect) {
-        return MenuUiModel.of(menuBarSelect, select(menuBarSelect));
+    public NavbarUiModel getMenu() {
+        return new NavbarUiModel(
+                buildNavBarSection(MenuBar.PRIMARY),
+                buildNavBarSection(MenuBar.SECONDARY),
+                buildNavBarSection(MenuBar.TERTIARY));
     }
 
-    @Override
-    public void buildMenuItems(
-            final MenuUiModel menuUiModel,
-            final MenuVisitor menuBuilder) {
+    // -- HELPER
 
-        val menuBars = menuBarsService.menuBars();
-        val menuBar = (BSMenuBar) 
menuBars.menuBarFor(menuUiModel.getMenuBarSelect());
+    private NavBarSection buildNavBarSection(final MenuBar menuBarSelect) {
 
-        _MenuItemBuilder.buildMenuItems(
-                metaModelContext,
-                menuBar,
-                menuBuilder);
+        val menuBar = (BSMenuBar) menuBarsService.menuBars()
+                .menuBarFor(menuBarSelect);
 
-    }
+        val topLevelEntries = new ArrayList<MenuDropdownBuilder>();
 
-    // -- HELPER
+        _MenuItemBuilder.buildMenuItems(metaModelContext, menuBar, new 
_MenuItemBuilder.Visitor() {
 
-    private List<String> select(final DomainServiceLayout.MenuBar 
menuBarSelect) {
-        return metaModelContext.streamServiceAdapters()
-                .filter(with(menuBarSelect))
-                .map(ManagedObject::getSpecification)
-                .map(ObjectSpecification::getLogicalTypeName)
-                .collect(Collectors.toList());
-    }
+            private MenuDropdownBuilder currentMenu;
+
+            @Override
+            public void addTopLevel(final MenuItemDto menuDto) {
+                topLevelEntries.add(currentMenu = new 
MenuDropdownBuilder(menuDto.getName(), new ArrayList<>()));
+            }
+
+            @Override
+            public void addSectionSpacer() {
+                currentMenu.addSectionSpacer();
+            }
+
+            @Override
+            public void addSectionLabel(final String named) {
+                currentMenu.addSectionSpacer(named);
+            }
+
+            @Override
+            public void addMenuAction(final MenuItemDto menuDto) {
+                val action = menuDto.getManagedAction();
+                currentMenu.addAction(action);
+            }
 
-    private static Predicate<ManagedObject> with(final 
DomainServiceLayout.MenuBar menuBarSelect) {
-        return (final ManagedObject adapter) ->
+        });
 
-            Facets.domainServiceLayoutMenuBar(adapter.getSpecification())
-                    .orElse(DomainServiceLayout.MenuBar.PRIMARY)
-                    .equals(menuBarSelect);
+        return new NavBarSection(menuBarSelect, 
Can.ofCollection(topLevelEntries)
+                .map(MenuDropdownBuilder::build));
     }
 
 }
diff --git 
a/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/menu/_MenuItemBuilder.java
 
b/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/menu/_MenuItemBuilder.java
index 77fe38c045..2e0ec4c10d 100644
--- 
a/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/menu/_MenuItemBuilder.java
+++ 
b/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/menu/_MenuItemBuilder.java
@@ -29,7 +29,6 @@ import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.interactions.managed.ManagedAction;
 import org.apache.causeway.viewer.commons.applib.services.menu.MenuItemDto;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuVisitor;
 import 
org.apache.causeway.viewer.commons.services.userprof.UserProfileUiServiceDefault;
 
 import lombok.NonNull;
@@ -40,10 +39,23 @@ import lombok.extern.log4j.Log4j2;
 @Log4j2
 final class _MenuItemBuilder {
 
-    public static void buildMenuItems(
+    static interface Visitor {
+
+        void addTopLevel(MenuItemDto menuDto);
+        void addSectionSpacer();
+        void addMenuAction(MenuItemDto menuDto);
+
+        /**
+         * @param named - not null and not empty
+         */
+        void addSectionLabel(String named);
+
+    }
+
+    static void buildMenuItems(
             final MetaModelContext mmc,
             final BSMenuBar menuBar,
-            final MenuVisitor menuBuilder) {
+            final Visitor menuBuilder) {
 
         val itemsPerSectionCounter = new LongAdder();
 
@@ -98,7 +110,7 @@ final class _MenuItemBuilder {
     private static class MenuProcessor {
 
         private final MetaModelContext metaModelContext;
-        private final MenuVisitor menuVisitor;
+        private final Visitor menuVisitor;
 
         private BSMenu currentTopLevel;
         private boolean pushedCurrentTopLevel = false;
@@ -143,7 +155,7 @@ final class _MenuItemBuilder {
                     actionLayoutData.getNamed(),
                     actionLayoutData.getCssClassFa());
 
-            menuVisitor.addSubMenu(menuDto);
+            menuVisitor.addMenuAction(menuDto);
         }
 
     }
diff --git 
a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/ServiceActionsModel.java
 
b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/ServiceActionsModel.java
index 30a48e220a..79d33b22ec 100644
--- 
a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/ServiceActionsModel.java
+++ 
b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/ServiceActionsModel.java
@@ -20,32 +20,32 @@ package org.apache.causeway.viewer.wicket.model.models;
 
 
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuUiModel;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.NavBarSection;
 /**
  * Backing model for actions of application services menu bar (typically, as
  * displayed along the top or side of the page).
  */
-public class ServiceActionsModel extends ModelAbstract<MenuUiModel> {
+public class ServiceActionsModel extends ModelAbstract<NavBarSection> {
 
     private static final long serialVersionUID = 1L;
 
-    private final MenuUiModel menuUiModel;
+    private final NavBarSection navBarSection;
 
     /**
      * @param commonContext
-     * @param menuUiModel - may be null in special case of rendering the 
tertiary menu on the error page.
+     * @param navBarSection - may be null in special case of rendering the 
tertiary menu on the error page.
      */
     public ServiceActionsModel(
             final MetaModelContext commonContext,
-            final MenuUiModel menuUiModel) {
+            final NavBarSection navBarSection) {
 
         super(commonContext);
-        this.menuUiModel = menuUiModel;
+        this.navBarSection = navBarSection;
     }
 
     @Override
-    protected MenuUiModel load() {
-        return menuUiModel;
+    protected NavBarSection load() {
+        return navBarSection;
     }
 
 }
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/entityactions/LinkAndLabelFactory.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/entityactions/LinkAndLabelFactory.java
index fa9111179d..a0e22380f8 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/entityactions/LinkAndLabelFactory.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/entityactions/LinkAndLabelFactory.java
@@ -21,9 +21,11 @@ package 
org.apache.causeway.viewer.wicket.ui.components.actionmenu.entityactions
 import java.util.function.Function;
 
 import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.ManagedObjects;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.MenuAction;
 import 
org.apache.causeway.viewer.wicket.model.links.ActionLinkUiComponentFactoryWkt;
 import org.apache.causeway.viewer.wicket.model.links.LinkAndLabel;
 import org.apache.causeway.viewer.wicket.model.models.ActionModel;
@@ -43,12 +45,14 @@ import lombok.val;
 public interface LinkAndLabelFactory
 extends Function<ObjectAction, LinkAndLabel> {
 
-    public static LinkAndLabelFactory forMenu(
-            final UiObjectWkt serviceModel) {
-        return action -> LinkAndLabel.of(
+    public static LinkAndLabel linkAndLabelForMenu(
+            @NonNull final MetaModelContext commonContext,
+            @NonNull final MenuAction menuAction) {
+        val serviceModel = UiObjectWkt.ofBookmark(commonContext, 
menuAction.serviceBookmark());
+        return LinkAndLabel.of(
                 ActionModelImpl.forEntity(
                         serviceModel,
-                        action.getFeatureIdentifier(),
+                        menuAction.actionId(),
                         Where.ANYWHERE,
                         null, null, null),
                 new MenuLinkFactory());
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java
index 8b58620648..dc271361fd 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java
@@ -26,12 +26,10 @@ import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.panel.Fragment;
 
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
-import org.apache.causeway.core.metamodel.interactions.managed.ManagedAction;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuItemDto;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuUiModel;
 import org.apache.causeway.viewer.commons.applib.services.menu.MenuVisitor;
-import org.apache.causeway.viewer.wicket.model.links.LinkAndLabel;
-import org.apache.causeway.viewer.wicket.model.models.UiObjectWkt;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.MenuAction;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.MenuDropdown;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.NavBarSection;
 import 
org.apache.causeway.viewer.wicket.ui.components.actionmenu.entityactions.LinkAndLabelFactory;
 import org.apache.causeway.viewer.wicket.ui.util.Wkt;
 import org.apache.causeway.viewer.wicket.ui.util.WktDecorators;
@@ -104,50 +102,37 @@ public final class ServiceActionUtil {
         private CssMenuItem currentTopLevelMenu = null;
 
         @Override
-        public void addTopLevel(final MenuItemDto menuDto) {
-            currentTopLevelMenu = CssMenuItem.newMenuItem(menuDto.getName());
+        public void onTopLevel(final MenuDropdown menuDto) {
+            currentTopLevelMenu = CssMenuItem.newMenuItem(menuDto.name());
             onNewMenuItem.accept(currentTopLevelMenu);
         }
 
         @Override
-        public void addSectionSpacer() {
+        public void onSectionSpacer() {
             val menuSection = CssMenuItem.newSpacer();
             currentTopLevelMenu.addSubMenuItem(menuSection);
         }
 
         @Override
-        public void addSubMenu(final MenuItemDto menuDto) {
-            val managedAction = menuDto.getManagedAction();
-
-            val menuItem = CssMenuItem.newMenuItem(menuDto.getName());
+        public void onMenuAction(final MenuAction menuAction) {
+            val menuItem = CssMenuItem.newMenuItem(menuAction.name());
             currentTopLevelMenu.addSubMenuItem(menuItem);
-
-            menuItem.setLinkAndLabel(newActionLink(managedAction));
+            
menuItem.setLinkAndLabel(LinkAndLabelFactory.linkAndLabelForMenu(commonContext, 
menuAction));
         }
 
         @Override
-        public void addSectionLabel(final String named) {
+        public void onSectionLabel(final String named) {
             val menuSectionLabel = CssMenuItem.newSectionLabel(named);
             currentTopLevelMenu.addSubMenuItem(menuSectionLabel);
         }
-
-        private LinkAndLabel newActionLink(
-                final ManagedAction managedAction) {
-
-            val serviceModel = UiObjectWkt.ofAdapter(commonContext, 
managedAction.getOwner());
-
-            return LinkAndLabelFactory.forMenu(serviceModel)
-                    .apply(managedAction.getAction());
-        }
-
     }
 
     public static void buildMenu(
             final MetaModelContext commonContext,
-            final MenuUiModel menuUiModel,
+            final NavBarSection navBarSection,
             final Consumer<CssMenuItem> onNewMenuItem) {
 
-        menuUiModel.buildMenuItems(commonContext.getMetaModelContext(),
+        navBarSection.visitMenuItems(
                 MenuBuilderWkt.of(
                         commonContext,
                         onNewMenuItem));
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java
index b971d865cd..4cb701ea9b 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java
@@ -50,8 +50,8 @@ public class ServiceActionsPanelFactory extends 
ComponentFactoryAbstract {
         if(!(model instanceof ServiceActionsModel)) {
             return ApplicationAdvice.DOES_NOT_APPLY;
         }
-        val menuUiModel = ((ServiceActionsModel) model).getObject();
-        val menuBarSelect = menuUiModel.getMenuBarSelect();
+        val navBarSection = ((ServiceActionsModel) model).getObject();
+        val menuBarSelect = navBarSection.menuBarSelect();
         return appliesIf(
                 menuBarSelect != DomainServiceLayout.MenuBar.TERTIARY
                 && menuBarSelect != null);
@@ -59,11 +59,11 @@ public class ServiceActionsPanelFactory extends 
ComponentFactoryAbstract {
 
     @Override
     public Component createComponent(final String id, final IModel<?> model) {
-        val menuUiModel = ((ServiceActionsModel) model).getObject();
+        val navBarSection = ((ServiceActionsModel) model).getObject();
 
         val menuItems = _Lists.<CssMenuItem>newArrayList();
         ServiceActionUtil.buildMenu(
-                super.getMetaModelContext(), menuUiModel, menuItems::add);
+                super.getMetaModelContext(), navBarSection, menuItems::add);
 
         return new ServiceActionsPanel(id, menuItems);
     }
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java
index cbdde29760..f30701c8dd 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java
@@ -50,7 +50,7 @@ public class TertiaryMenuPanelFactory extends 
ComponentFactoryAbstract {
             return ApplicationAdvice.DOES_NOT_APPLY;
         }
         val menuUiModel = ((ServiceActionsModel) model).getObject();
-        val menuBarSelect = menuUiModel.getMenuBarSelect();
+        val menuBarSelect = menuUiModel.menuBarSelect();
         return appliesIf(
                 menuBarSelect == DomainServiceLayout.MenuBar.TERTIARY
                 || menuBarSelect == null);
@@ -58,11 +58,11 @@ public class TertiaryMenuPanelFactory extends 
ComponentFactoryAbstract {
 
     @Override
     public Component createComponent(final String id, final IModel<?> model) {
-        val menuUiModel = ((ServiceActionsModel) model).getObject();
+        val navBarSection = ((ServiceActionsModel) model).getObject();
 
         val menuItems = _Lists.<CssMenuItem>newArrayList();
         ServiceActionUtil.buildMenu(
-                super.getMetaModelContext(), menuUiModel, menuItems::add);
+                super.getMetaModelContext(), navBarSection, menuItems::add);
 
         return new TertiaryActionsPanel(id, menuItems);
     }
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/header/HeaderPanel.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/header/HeaderPanel.java
index 43fcc8046c..ae1de90d13 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/header/HeaderPanel.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/header/HeaderPanel.java
@@ -25,7 +25,7 @@ import 
org.apache.wicket.request.mapper.parameter.PageParameters;
 
 import 
org.apache.causeway.viewer.commons.applib.services.branding.BrandingUiModel;
 import org.apache.causeway.viewer.commons.applib.services.header.HeaderUiModel;
-import org.apache.causeway.viewer.commons.applib.services.menu.MenuUiModel;
+import 
org.apache.causeway.viewer.commons.applib.services.menu.model.NavBarSection;
 import 
org.apache.causeway.viewer.commons.applib.services.userprof.UserProfileUiModel;
 import org.apache.causeway.viewer.commons.model.components.UiComponentType;
 import org.apache.causeway.viewer.wicket.model.models.ServiceActionsModel;
@@ -108,26 +108,27 @@ extends PanelAbstract<String, Model<String>> {
     }
 
     protected void addServiceActionMenuBars(final HeaderUiModel headerUiModel) 
{
+        val navbar = headerUiModel.getNavbar();
         if (getPage() instanceof ErrorPage) {
             WktComponents.permanentlyHide(this, ID_PRIMARY_MENU_BAR);
             WktComponents.permanentlyHide(this, ID_SECONDARY_MENU_BAR);
-            addMenuBar(ID_TERTIARY_MENU_BAR, headerUiModel.getTertiary());
+            addMenuBar(ID_TERTIARY_MENU_BAR, navbar.tertiary());
         } else {
-            addMenuBar(ID_PRIMARY_MENU_BAR, headerUiModel.getPrimary());
-            addMenuBar(ID_SECONDARY_MENU_BAR, headerUiModel.getSecondary());
-            addMenuBar(ID_TERTIARY_MENU_BAR, headerUiModel.getTertiary());
+            addMenuBar(ID_PRIMARY_MENU_BAR, navbar.primary());
+            addMenuBar(ID_SECONDARY_MENU_BAR, navbar.secondary());
+            addMenuBar(ID_TERTIARY_MENU_BAR, navbar.tertiary());
         }
     }
 
     private void addMenuBar(
             final String id,
-            final MenuUiModel menuUiModel) {
+            final NavBarSection menuUiModel) {
 
         final MarkupContainer container = this;
         val menuModel = new ServiceActionsModel(super.getMetaModelContext(), 
menuUiModel);
         val menuBarComponent = getComponentFactoryRegistry()
                 .createComponent(id, UiComponentType.SERVICE_ACTIONS, 
menuModel);
-        Wkt.cssAppend(menuBarComponent, menuUiModel.getCssClass());
+        Wkt.cssAppend(menuBarComponent, menuUiModel.cssClass());
         container.add(menuBarComponent);
     }
 

Reply via email to