diff --git a/components/breadcrumbs.js b/components/breadcrumbs.js
index 0e4d513a..672f9a0e 100644
--- a/components/breadcrumbs.js
+++ b/components/breadcrumbs.js
@@ -166,6 +166,62 @@ const Breadcrumbs = (props) => {
return simpleLinkTextPairRenderersByPath[path] || null;
};
+ const getSimpleMyPortalRouteRenderer = (path) => {
+ const simpleMyPortalRouteRenderersByPath = {
+ "/myportal/searchresults": () => (
+ <>
+ {renderAnchorCrumb(
+ myPortalHref,
+ t("common:breadcrumb-my-portal")
+ )}
+ {renderTextCrumb(t("common:breadcrumb-search-results"))}
+ >
+ ),
+ "/myportal/addresssearch": () =>
+ renderMyPortalSectionCrumbs(
+ t("common:breadcrumb-address-search")
+ ),
+ "/myportal/advancedsearch": () =>
+ renderMyPortalSectionCrumbs(
+ t("common:breadcrumb-advanced-search")
+ ),
+ "/myportal/contactus": () =>
+ renderMyPortalSectionCrumbs(
+ t("common:footer-contact-us-link-label")
+ ),
+ "/myportal/viewall": () =>
+ renderMyPortalSectionCrumbs(
+ getViewAllLabel(
+ currentViewState.viewKey || router.query.key
+ )
+ )
+ };
+
+ return simpleMyPortalRouteRenderersByPath[path] || null;
+ };
+
+ const resolveMappedRouteRenderer = (path) => {
+ const mappedResolvers = [
+ getSimpleRouteRenderer,
+ getSimpleLinkTextPairRenderer,
+ getSimpleMyPortalRouteRenderer
+ ];
+
+ for (const resolveRenderer of mappedResolvers) {
+ const routeRenderer = resolveRenderer(path);
+ if (routeRenderer) {
+ return routeRenderer;
+ }
+ }
+
+ return null;
+ };
+
+ const renderMappedRoute = (path, resolver = resolveMappedRouteRenderer) => {
+ const routeRenderer = resolver(path);
+ return routeRenderer ? routeRenderer() : null;
+ };
+
const renderMyPortalCrumb = () => (
@@ -261,43 +317,7 @@ const Breadcrumbs = (props) => {
t("common:service-name-breadcrumb")
)}
- {(() => {
- const renderSimpleRoute =
- getSimpleRouteRenderer(pathname);
- return renderSimpleRoute
- ? renderSimpleRoute()
- : null;
- })()}
- {(() => {
- const renderSimpleLinkTextPair =
- getSimpleLinkTextPairRenderer(pathname);
- return renderSimpleLinkTextPair
- ? renderSimpleLinkTextPair()
- : null;
- })()}
- {isPath("/myportal/searchresults") && (
- <>
- {renderAnchorCrumb(
- myPortalHref,
- t("common:breadcrumb-my-portal")
- )}
-
- {t("common:breadcrumb-search-results")}
-
- >
- )}
- {isPath("/myportal/addresssearch") &&
- renderMyPortalSectionCrumbs(
- t("common:breadcrumb-address-search")
- )}
- {isPath("/myportal/advancedsearch") &&
- renderMyPortalSectionCrumbs(
- t("common:breadcrumb-advanced-search")
- )}
- {isPath("/myportal/contactus") &&
- renderMyPortalSectionCrumbs(
- t("common:footer-contact-us-link-label")
- )}
+ {renderMappedRoute(pathname)}
{isPath("/myportal/advancedsearchresults") && (
<>
{renderMyPortalCrumb()}
@@ -386,17 +406,6 @@ const Breadcrumbs = (props) => {
>
)}
- {isPath("/myportal/viewall") && (
- <>
- {renderMyPortalCrumb()}
-
- {getViewAllLabel(
- currentViewState.viewKey ||
- router.query.key
- )}
-
- >
- )}
{isPath("/myportal/case") && (
<>
{renderLinkCrumb(
diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md
index be7d909b..1fab1697 100644
--- a/memory-bank/change-log.md
+++ b/memory-bank/change-log.md
@@ -392,6 +392,43 @@ Follow-ups:
- Next slice recommendation: introduce a tiny local `renderMappedRoute(path, resolver)` helper to remove repeated inline IIFE map invocations, then optionally merge simple maps under one top-level resolver while preserving explicit ordering and side-effect-free constraints.
+### CL-22541-N: breadcrumbs unified mapped-route resolver + widened structural guardrails
+
+date: 2026-04-09
+author: Cline
+scope: `components/breadcrumbs.js`, `tests/phase22/breadcrumbs-route-map-structure.test.cjs`
+type: change
+rationale: Execute the requested wider next slice by removing duplicated mapped-route invocation patterns, introducing a single top-level mapped resolver with explicit precedence, and migrating an additional safe my-portal route group into map-driven rendering while preserving explicit dynamic/callback branches.
+impact: Refactor-only structural consolidation/readability improvement with widened mapped-route coverage and stronger structure guardrail tests; no intended route/auth/session/API/EN-CY/a11y behavior change.
+status: completed
+
+Summary:
+
+- Added `getSimpleMyPortalRouteRenderer(path)` in `components/breadcrumbs.js` and migrated additional safe map-friendly routes:
+ - `/myportal/searchresults`
+ - `/myportal/addresssearch`
+ - `/myportal/advancedsearch`
+ - `/myportal/contactus`
+ - `/myportal/viewall`
+- Added `resolveMappedRouteRenderer(path)` to unify map precedence under one resolver.
+- Added `renderMappedRoute(path, resolver?)` helper and replaced duplicated inline map IIFE invocations with a single call:
+ - `{renderMappedRoute(pathname)}`
+- Kept dynamic/stateful callback branches explicit and unchanged, especially `/myportal/case/id/[incident]` (`router.back()`) and other callback/state driven branches.
+- Expanded `tests/phase22/breadcrumbs-route-map-structure.test.cjs` to assert:
+ - new my-portal route-map entries are present
+ - unified resolver/helper signatures are present
+ - single mapped-route invocation is used in JSX
+ - dynamic callback branch remains explicit.
+
+Validation:
+
+- `npx eslint components/breadcrumbs.js tests/phase22/breadcrumbs-route-map-structure.test.cjs tests/phase22/index.test.cjs` -> pass.
+- `node tests/phase22/index.test.cjs` -> pass (combined suite; breadcrumbs-route-map tests 3/3).
+
+Follow-ups:
+
+- Next wider slice candidate: move a second safe myportal results pair group (`/myportal/advancedsearchresults`, `/myportal/addresssearchresults`) into the unified mapped resolver where callback-free, then consider splitting resolver maps into a dedicated local constant section for improved scanability.
+
### CL-00X: 22500 `components/elements/index.js` Phase 1 helper extraction
date: 2026-04-07
diff --git a/tests/phase22/breadcrumbs-route-map-structure.test.cjs b/tests/phase22/breadcrumbs-route-map-structure.test.cjs
index c3a6f99c..9727ca6c 100644
--- a/tests/phase22/breadcrumbs-route-map-structure.test.cjs
+++ b/tests/phase22/breadcrumbs-route-map-structure.test.cjs
@@ -30,6 +30,42 @@ test("breadcrumbs/simple-link-text map includes address and advanced search resu
true,
"Expected /advancedsearchresults to be mapped in getSimpleLinkTextPairRenderer"
);
+
+ assert.strictEqual(
+ source.includes('"/myportal/searchresults": () => ('),
+ true,
+ "Expected /myportal/searchresults to be mapped in getSimpleMyPortalRouteRenderer"
+ );
+
+ assert.strictEqual(
+ source.includes('"/myportal/viewall": () =>'),
+ true,
+ "Expected /myportal/viewall to be mapped in getSimpleMyPortalRouteRenderer"
+ );
+});
+
+test("breadcrumbs/merged resolver renders mapped routes through single helper invocation", async () => {
+ const source = loadBreadcrumbSource();
+
+ assert.strictEqual(
+ source.includes("const resolveMappedRouteRenderer = (path) =>"),
+ true,
+ "Expected unified mapped route resolver helper to exist"
+ );
+
+ assert.strictEqual(
+ source.includes(
+ "const renderMappedRoute = (path, resolver = resolveMappedRouteRenderer) =>"
+ ),
+ true,
+ "Expected reusable renderMappedRoute helper to exist"
+ );
+
+ assert.strictEqual(
+ source.includes("{renderMappedRoute(pathname)}"),
+ true,
+ "Expected breadcrumb JSX to use single mapped route render invocation"
+ );
});
test("breadcrumbs/dynamic callback branch remains explicit for myportal case incident route", async () => {