diff --git a/components/breadcrumbs.js b/components/breadcrumbs.js
index 23e17a77..17985d09 100644
--- a/components/breadcrumbs.js
+++ b/components/breadcrumbs.js
@@ -213,6 +213,7 @@ const Breadcrumbs = (props) => {
simpleRouteRenderersByPath,
simpleLinkTextPairRenderersByPath,
simpleMyPortalRouteRenderersByPath,
+ newAppealRouteRenderersByPath,
caseDetailRouteRenderersByPath,
detailAndAccountRouteRenderersByPath
} = createMappedRouteRendererGroups({
@@ -242,6 +243,7 @@ const Breadcrumbs = (props) => {
simpleRouteRenderersByPath,
simpleLinkTextPairRenderersByPath,
simpleMyPortalRouteRenderersByPath,
+ newAppealRouteRenderersByPath,
caseDetailRouteRenderersByPath,
detailAndAccountRouteRenderersByPath
});
@@ -306,22 +308,6 @@ const Breadcrumbs = (props) => {
>
)}
- {isPath("/newappeal/selectappeal") && (
- <>
- {renderLinkCrumb(
- myPortalHref,
- t("newappeal:parent-page-title")
- )}
- {renderLinkCrumb(
- myPortalHref,
- t("newappeal:page-title")
- )}
-
- Select Appeal
-
- >
- )}
-
{isPath("/myportal/case/id/[incident]") && (
<>
{renderMyPortalCrumb()}
diff --git a/lib/routing/breadcrumbRendererFactories.js b/lib/routing/breadcrumbRendererFactories.js
index 21c3024b..ad9efc92 100644
--- a/lib/routing/breadcrumbRendererFactories.js
+++ b/lib/routing/breadcrumbRendererFactories.js
@@ -107,6 +107,21 @@ export const createSimpleMyPortalRouteRenderers = ({
)
});
+export const createNewAppealRouteRenderers = ({
+ t,
+ myPortalHref,
+ renderLinkCrumb,
+ renderTextCrumb
+}) => ({
+ "/newappeal/selectappeal": () => (
+ <>
+ {renderLinkCrumb(myPortalHref, t("newappeal:parent-page-title"))}
+ {renderLinkCrumb(myPortalHref, t("newappeal:page-title"))}
+ {renderTextCrumb("Select Appeal")}
+ >
+ )
+});
+
export const createCaseDetailRouteRenderers = ({
t,
router,
@@ -396,6 +411,13 @@ export const createMappedRouteRendererGroups = ({
renderMyPortalSectionCrumbs
});
+ const newAppealRouteRenderersByPath = createNewAppealRouteRenderers({
+ t,
+ myPortalHref,
+ renderLinkCrumb,
+ renderTextCrumb
+ });
+
const caseDetailRouteRenderersByPath = createCaseDetailRouteRenderers({
t,
router,
@@ -430,6 +452,7 @@ export const createMappedRouteRendererGroups = ({
simpleRouteRenderersByPath,
simpleLinkTextPairRenderersByPath,
simpleMyPortalRouteRenderersByPath,
+ newAppealRouteRenderersByPath,
caseDetailRouteRenderersByPath,
detailAndAccountRouteRenderersByPath
};
diff --git a/lib/routing/breadcrumbRouteMaps.js b/lib/routing/breadcrumbRouteMaps.js
index 3d505eb1..c3cbde18 100644
--- a/lib/routing/breadcrumbRouteMaps.js
+++ b/lib/routing/breadcrumbRouteMaps.js
@@ -16,12 +16,14 @@ const buildBreadcrumbRendererMaps = ({
simpleRouteRenderersByPath,
simpleLinkTextPairRenderersByPath,
simpleMyPortalRouteRenderersByPath,
+ newAppealRouteRenderersByPath,
caseDetailRouteRenderersByPath,
detailAndAccountRouteRenderersByPath
}) => [
simpleRouteRenderersByPath,
simpleLinkTextPairRenderersByPath,
simpleMyPortalRouteRenderersByPath,
+ newAppealRouteRenderersByPath,
caseDetailRouteRenderersByPath,
detailAndAccountRouteRenderersByPath
];
diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md
index bf772e1f..1a07eb6c 100644
--- a/memory-bank/change-log.md
+++ b/memory-bank/change-log.md
@@ -602,6 +602,42 @@ Follow-ups:
- Next slice option A (larger): evaluate whether any remaining deterministic inline branches can be extracted without violating callback/state constraints.
- Next slice option B (smaller): add a focused helper-level precedence test where earlier map contains falsy/non-function values and later maps contain valid renderers.
+### CL-22541-T: breadcrumbs larger slice — new-appeal mapped group extraction (`/newappeal/selectappeal`)
+
+date: 2026-04-09
+author: Cline
+scope: `lib/routing/{breadcrumbRendererFactories,breadcrumbRouteMaps}.js`, `components/breadcrumbs.js`, `tests/phase22/{breadcrumb-route-maps-helper,breadcrumbs-route-map-structure}.test.cjs`
+type: change
+rationale: Execute the next larger deterministic refactor slice by extracting callback-free new-appeal breadcrumb branching into a dedicated mapped renderer group and extending map-order guardrails.
+impact: Refactor-only structural extraction plus precedence-test hardening; no intended route/auth/session/API/EN-CY/a11y behavior change.
+status: completed
+
+Summary:
+
+- Added `createNewAppealRouteRenderers(...)` in `lib/routing/breadcrumbRendererFactories.js`.
+- Mapped `/newappeal/selectappeal` into the new grouped renderer factory.
+- Extended grouped composition return from `createMappedRouteRendererGroups(...)` with `newAppealRouteRenderersByPath`.
+- Updated `lib/routing/breadcrumbRouteMaps.js` to include `newAppealRouteRenderersByPath` in explicit map precedence ordering.
+- Updated `components/breadcrumbs.js` to consume/pass `newAppealRouteRenderersByPath` into map builder.
+- Removed now-redundant inline `isPath("/newappeal/selectappeal")` branch from component JSX.
+- Expanded tests:
+ - `tests/phase22/breadcrumbs-route-map-structure.test.cjs`
+ - assert new factory export and mapped route presence
+ - assert inline branch removal
+ - assert updated map-order invariant (simple -> link-text -> myportal -> new-appeal -> case detail -> detail/account)
+ - `tests/phase22/breadcrumb-route-maps-helper.test.cjs`
+ - assert six-map ordering contract and updated grouped precedence wiring.
+
+Validation:
+
+- `npx eslint lib/routing/breadcrumbRendererFactories.js lib/routing/breadcrumbRouteMaps.js components/breadcrumbs.js tests/phase22/breadcrumb-route-maps-helper.test.cjs tests/phase22/breadcrumbs-route-map-structure.test.cjs tests/phase22/index.test.cjs` -> pass.
+- `node tests/phase22/index.test.cjs` -> pass (combined suite).
+
+Follow-ups:
+
+- Next larger slice candidate: evaluate extraction viability for `/case` branch only if callback/`router.back()` behavior can remain explicit and parity-safe.
+- Next smaller slice candidate: add helper-level test coverage for unexpected non-function map values and explicit skip behavior (defensive precedence guard).
+
### CL-00X: 22500 `components/elements/index.js` Phase 1 helper extraction
date: 2026-04-07
diff --git a/tests/phase22/breadcrumb-route-maps-helper.test.cjs b/tests/phase22/breadcrumb-route-maps-helper.test.cjs
index 60ab8661..23c40e38 100644
--- a/tests/phase22/breadcrumb-route-maps-helper.test.cjs
+++ b/tests/phase22/breadcrumb-route-maps-helper.test.cjs
@@ -57,6 +57,9 @@ test("buildBreadcrumbRendererMaps returns grouped maps in explicit precedence or
const simpleMyPortalRouteRenderersByPath = {
"/gamma": () => "gamma"
};
+ const newAppealRouteRenderersByPath = {
+ "/eta": () => "eta"
+ };
const caseDetailRouteRenderersByPath = {
"/delta": () => "delta"
};
@@ -68,12 +71,13 @@ test("buildBreadcrumbRendererMaps returns grouped maps in explicit precedence or
simpleRouteRenderersByPath,
simpleLinkTextPairRenderersByPath,
simpleMyPortalRouteRenderersByPath,
+ newAppealRouteRenderersByPath,
caseDetailRouteRenderersByPath,
detailAndAccountRouteRenderersByPath
});
assert.ok(Array.isArray(result), "Expected grouped maps array");
- assert.strictEqual(result.length, 5, "Expected five grouped route maps");
+ assert.strictEqual(result.length, 6, "Expected six grouped route maps");
assert.strictEqual(
result[0],
simpleRouteRenderersByPath,
@@ -91,13 +95,18 @@ test("buildBreadcrumbRendererMaps returns grouped maps in explicit precedence or
);
assert.strictEqual(
result[3],
- caseDetailRouteRenderersByPath,
- "Expected case detail route renderers fourth"
+ newAppealRouteRenderersByPath,
+ "Expected new-appeal route renderers fourth"
);
assert.strictEqual(
result[4],
+ caseDetailRouteRenderersByPath,
+ "Expected case detail route renderers fifth"
+ );
+ assert.strictEqual(
+ result[5],
detailAndAccountRouteRenderersByPath,
- "Expected detail and account route renderers fifth"
+ "Expected detail and account route renderers sixth"
);
});
@@ -116,6 +125,7 @@ test("resolveMappedRouteRenderer returns renderer from first matching grouped ma
"/known": secondRenderer
},
simpleMyPortalRouteRenderersByPath: {},
+ newAppealRouteRenderersByPath: {},
caseDetailRouteRenderersByPath: {},
detailAndAccountRouteRenderersByPath: {}
});
@@ -138,6 +148,9 @@ test("resolveMappedRouteRenderer returns renderer from later grouped map when ea
const maps = buildBreadcrumbRendererMaps({
simpleRouteRenderersByPath: {},
simpleLinkTextPairRenderersByPath: {},
+ newAppealRouteRenderersByPath: {
+ "/newappeal/selectappeal": () => "newappeal"
+ },
simpleMyPortalRouteRenderersByPath: {},
caseDetailRouteRenderersByPath: {},
detailAndAccountRouteRenderersByPath: {
@@ -164,6 +177,7 @@ test("resolveMappedRouteRenderer returns null for unmapped path", () => {
},
simpleLinkTextPairRenderersByPath: {},
simpleMyPortalRouteRenderersByPath: {},
+ newAppealRouteRenderersByPath: {},
caseDetailRouteRenderersByPath: {},
detailAndAccountRouteRenderersByPath: {}
});
diff --git a/tests/phase22/breadcrumbs-route-map-structure.test.cjs b/tests/phase22/breadcrumbs-route-map-structure.test.cjs
index 60cb5a27..5cf9d452 100644
--- a/tests/phase22/breadcrumbs-route-map-structure.test.cjs
+++ b/tests/phase22/breadcrumbs-route-map-structure.test.cjs
@@ -36,6 +36,12 @@ test("breadcrumbs/factory module includes expected mapped route definitions", as
"Expected my-portal route renderer factory export"
);
+ assert.strictEqual(
+ source.includes("export const createNewAppealRouteRenderers ="),
+ true,
+ "Expected new-appeal route renderer factory export"
+ );
+
assert.strictEqual(
source.includes("export const createCaseDetailRouteRenderers ="),
true,
@@ -84,6 +90,12 @@ test("breadcrumbs/factory module includes expected mapped route definitions", as
"Expected /myportal/addresssearchresults to be mapped in createSimpleMyPortalRouteRenderers"
);
+ assert.strictEqual(
+ source.includes('"/newappeal/selectappeal": () => ('),
+ true,
+ "Expected /newappeal/selectappeal to be mapped in createNewAppealRouteRenderers"
+ );
+
assert.strictEqual(
source.includes('"/case/[ticketnumber]": () =>'),
true,
@@ -173,7 +185,7 @@ test("breadcrumbs/component composes mapped routes via imported factories and sh
);
assert.strictEqual(
- /const\s+\{[\s\S]*simpleRouteRenderersByPath[\s\S]*detailAndAccountRouteRenderersByPath[\s\S]*\}\s*=\s*createMappedRouteRendererGroups\s*\(/.test(
+ /const\s+\{[\s\S]*simpleRouteRenderersByPath[\s\S]*newAppealRouteRenderersByPath[\s\S]*detailAndAccountRouteRenderersByPath[\s\S]*\}\s*=\s*createMappedRouteRendererGroups\s*\(/.test(
source
),
true,
@@ -254,6 +266,12 @@ test("breadcrumbs/component composes mapped routes via imported factories and sh
"Expected /case/id/[incident] explicit branch to be removed after mapping"
);
+ assert.strictEqual(
+ source.includes('isPath("/newappeal/selectappeal")'),
+ false,
+ "Expected /newappeal/selectappeal explicit branch to be removed after mapping"
+ );
+
assert.strictEqual(
source.includes('isPath("/myportal/case/[ticketnumber]")'),
false,
@@ -330,6 +348,9 @@ test("breadcrumbs/map builder preserves route map ordering", async () => {
const simpleMyPortalOrderIndex = mapBuilderSlice.indexOf(
"simpleMyPortalRouteRenderersByPath"
);
+ const newAppealOrderIndex = mapBuilderSlice.indexOf(
+ "newAppealRouteRenderersByPath"
+ );
const caseDetailOrderIndex = mapBuilderSlice.indexOf(
"caseDetailRouteRenderersByPath"
);
@@ -341,10 +362,11 @@ test("breadcrumbs/map builder preserves route map ordering", async () => {
simpleRouteOrderIndex >= 0 &&
simpleLinkTextPairOrderIndex > simpleRouteOrderIndex &&
simpleMyPortalOrderIndex > simpleLinkTextPairOrderIndex &&
- caseDetailOrderIndex > simpleMyPortalOrderIndex &&
+ newAppealOrderIndex > simpleMyPortalOrderIndex &&
+ caseDetailOrderIndex > newAppealOrderIndex &&
detailAndAccountOrderIndex > caseDetailOrderIndex,
true,
- "Expected map builder call to preserve explicit route-map ordering (simple -> link-text -> myportal -> case detail -> detail/account)"
+ "Expected map builder call to preserve explicit route-map ordering (simple -> link-text -> myportal -> new-appeal -> case detail -> detail/account)"
);
});