From 3aa086e129df24a3ad9cffea97cbb63c2b1320dd Mon Sep 17 00:00:00 2001 From: "Eduardo A." Date: Sat, 26 Sep 2026 17:27:05 -0300 Subject: [PATCH 1/2] fix(frontend): breadcrumb names the module and tab and links to real locations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JUM-906. AppBreadcrumb rendered route.matched[].path — the route pattern — so a module page read "Home / Home" and linked to /#/m/:moduleId/:tab?. Crumbs now resolve to real locations and the Module route shows the module title and the active tab. Also fix the frontend typecheck, which failed on dev with findLast (ES2023) under an ES2022 lib. Co-Authored-By: Claude Opus 5.5 --- .../frontend/src/components/AppBreadcrumb.vue | 71 +++++++++++++------ apps/frontend/src/data/canaSchema.ts | 2 +- .../test/component/AppBreadcrumb.test.ts | 64 +++++++++++++++++ test-map.json | 16 ++++- 4 files changed, 127 insertions(+), 26 deletions(-) create mode 100644 apps/frontend/test/component/AppBreadcrumb.test.ts diff --git a/apps/frontend/src/components/AppBreadcrumb.vue b/apps/frontend/src/components/AppBreadcrumb.vue index 2a375e449..729622cc0 100644 --- a/apps/frontend/src/components/AppBreadcrumb.vue +++ b/apps/frontend/src/components/AppBreadcrumb.vue @@ -1,44 +1,69 @@ diff --git a/apps/frontend/src/data/canaSchema.ts b/apps/frontend/src/data/canaSchema.ts index 8fddf9d50..64315ae8f 100644 --- a/apps/frontend/src/data/canaSchema.ts +++ b/apps/frontend/src/data/canaSchema.ts @@ -32,7 +32,7 @@ const storeNameFromListPath = (operationId: string, schemaName: string): string for (const [path, methods] of Object.entries(document.paths ?? {})) { for (const operation of Object.values(methods)) { if (operation?.operationId === operationId) { - const segment = path.split('/').filter(Boolean).findLast(Boolean); + const segment = path.split('/').filter(Boolean).at(-1); if (segment) return segment; } } diff --git a/apps/frontend/test/component/AppBreadcrumb.test.ts b/apps/frontend/test/component/AppBreadcrumb.test.ts new file mode 100644 index 000000000..1eff48709 --- /dev/null +++ b/apps/frontend/test/component/AppBreadcrumb.test.ts @@ -0,0 +1,64 @@ +import { + afterEach, describe, expect, it +} from 'bun:test'; +import { createMemoryHistory, createRouter } from 'vue-router'; + +import AppBreadcrumb from '@/components/AppBreadcrumb.vue'; +import '@/modules/index'; + +import { flush, freshSession, mountWithShell } from './support'; + +/** + * JUM-906: the breadcrumb read `route.matched[].path`, which is the route + * pattern — a module page showed "Home / Home" and linked to + * `/#/m/:moduleId/:tab?`. + */ +const routes = [ + { + path: '/', + name: 'Home', + component: { template: '' }, + meta: { titleKey: 'nav.home' }, + children: [ + { + path: '/m/:moduleId/:tab?', name: 'Module', component: { template: '
' }, meta: { titleKey: 'nav.home' } + } + ] + } +]; + +async function crumbsAt(path: string) { + const pinia = freshSession(); + const router = createRouter({ history: createMemoryHistory(), routes }); + await router.push(path); + const wrapper = mountWithShell(AppBreadcrumb, { pinia, global: { plugins: [pinia, router] } }); + await flush(2); + const items = wrapper.findAll('.breadcrumb-item').map((item) => ({ + text: item.text(), + href: item.find('a').exists() ? item.find('a').attributes('href') : '' + })); + wrapper.unmount(); + return items; +} + +describe('AppBreadcrumb (JUM-906)', () => { + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('names the module and the active tab instead of repeating Home', async () => { + expect.hasAssertions(); + const items = await crumbsAt('/m/users/dashboard'); + + expect(items.map((item) => item.text)).toStrictEqual(['Home', 'Users', 'Dashboard']); + }); + + it('links to resolved locations, never to a route pattern', async () => { + expect.hasAssertions(); + const items = await crumbsAt('/m/users/organizations'); + + expect(items.map((item) => item.text)).toStrictEqual(['Home', 'Users', 'Organizations']); + expect(items.some((item) => item.href.includes(':moduleId'))).toBe(false); + expect(items[1].href).toBe('/m/users'); + }); +}); diff --git a/test-map.json b/test-map.json index a181ba7dd..7a4597b66 100644 --- a/test-map.json +++ b/test-map.json @@ -2651,6 +2651,18 @@ "tier": "gate", "timeoutMs": 60000 }, + { + "id": "apps/frontend/test/component/AppBreadcrumb.test.ts", + "path": "apps/frontend/test/component/AppBreadcrumb.test.ts", + "layer": "frontend", + "kind": "non-hexagonal", + "type": "unit", + "runner": "bun", + "script": "frontend:test:unit", + "reason": "bun:test suites mounting shipped .vue components through @vue/test-utils + happy-dom (JUM-776); the app-scoped bunfig preload registers the SFC loader.", + "tier": "gate", + "timeoutMs": 60000 + }, { "id": "apps/frontend/test/component/ModuleLayout.test.ts", "path": "apps/frontend/test/component/ModuleLayout.test.ts", @@ -6035,9 +6047,9 @@ "shadowEnv": "JUMENTIX_GATE_V2_SHADOW" }, "stats": { - "unit": 357, + "unit": 358, "integration": 102, "smoke": 2, - "suites": 520 + "suites": 521 } } From ba5e95ea69a29d1df358c5a621288b0de1f16871 Mon Sep 17 00:00:00 2001 From: "Eduardo A." Date: Sat, 26 Sep 2026 18:20:46 -0300 Subject: [PATCH 2/2] chore(cli-init): rebuild packaged frontend templates for the breadcrumb fix The frontend seed changed (AppBreadcrumb.vue, canaSchema.ts), so the packaged templates were stale and `cli:check-template-freshness` failed the branch gate (CircleCI job 2630). Refs: JUM-906 Co-Authored-By: Claude Opus 5.5 --- packages/cli-init/templates.manifest.json | 6 +- .../frontend/src/components/AppBreadcrumb.vue | 71 +++++++++++++------ .../templates/frontend/src/data/canaSchema.ts | 2 +- 3 files changed, 52 insertions(+), 27 deletions(-) diff --git a/packages/cli-init/templates.manifest.json b/packages/cli-init/templates.manifest.json index 999495701..e7e82fd78 100644 --- a/packages/cli-init/templates.manifest.json +++ b/packages/cli-init/templates.manifest.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceCommit": "d038d9ebf8b2d0e234ea7c12c6336f356d4b1595", + "sourceCommit": "2a9a981e16853b7596716e505844f22611d2b506", "exclusions": [ "**/.agents/**", "**/.claude/**", @@ -3788,7 +3788,7 @@ "source": "apps/frontend/src/App.vue" }, "frontend/src/components/AppBreadcrumb.vue": { - "sha256": "6f3b663e86a6e799b1e424336424f0bf81101e2d6e9c528c39c0e464d910244c", + "sha256": "f47862797e201e7c32a01047baa06c52e6574a32e06c824e7924867c8d9b9dff", "source": "apps/frontend/src/components/AppBreadcrumb.vue" }, "frontend/src/components/AppFooter.vue": { @@ -3972,7 +3972,7 @@ "source": "apps/frontend/src/contracts/validation.ts" }, "frontend/src/data/canaSchema.ts": { - "sha256": "358e8e3e26e9b6266b89d7ef568941018d4d5ba19255827cf153cc61605e05d0", + "sha256": "1d34048e3b810346d445ea334fb03ac7391521c4fe6149391e278cfddd37b2d9", "source": "apps/frontend/src/data/canaSchema.ts" }, "frontend/src/data/db.ts": { diff --git a/packages/cli-init/templates/frontend/src/components/AppBreadcrumb.vue b/packages/cli-init/templates/frontend/src/components/AppBreadcrumb.vue index 2a375e449..729622cc0 100644 --- a/packages/cli-init/templates/frontend/src/components/AppBreadcrumb.vue +++ b/packages/cli-init/templates/frontend/src/components/AppBreadcrumb.vue @@ -1,44 +1,69 @@ diff --git a/packages/cli-init/templates/frontend/src/data/canaSchema.ts b/packages/cli-init/templates/frontend/src/data/canaSchema.ts index 8fddf9d50..64315ae8f 100644 --- a/packages/cli-init/templates/frontend/src/data/canaSchema.ts +++ b/packages/cli-init/templates/frontend/src/data/canaSchema.ts @@ -32,7 +32,7 @@ const storeNameFromListPath = (operationId: string, schemaName: string): string for (const [path, methods] of Object.entries(document.paths ?? {})) { for (const operation of Object.values(methods)) { if (operation?.operationId === operationId) { - const segment = path.split('/').filter(Boolean).findLast(Boolean); + const segment = path.split('/').filter(Boolean).at(-1); if (segment) return segment; } }