diff --git a/backend/version.py b/backend/version.py
index 7ec367d..35e2d0e 100644
--- a/backend/version.py
+++ b/backend/version.py
@@ -1,3 +1,3 @@
-APP_VERSION = "0.16.3-ap1.9a"
+APP_VERSION = "0.16.4-ap1.9b"
DB_SCHEMA_VERSION = "014"
APP_NAME = "jinkendo-kairo"
diff --git a/frontend/src/components/ProgramScopeBar.jsx b/frontend/src/components/ProgramScopeBar.jsx
index 688af45..4907b00 100644
--- a/frontend/src/components/ProgramScopeBar.jsx
+++ b/frontend/src/components/ProgramScopeBar.jsx
@@ -1,8 +1,13 @@
-import { Link } from 'react-router-dom'
+import { Link, useLocation } from 'react-router-dom'
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
-import { initiativeCatalogPath, scopedPath } from '../utils/routes.js'
+import { initiativeCatalogPath, projectPath, scopedPath } from '../utils/routes.js'
+import {
+ resolveInitiativeHub,
+ resolvePortfolioHub,
+} from '../utils/scopeNavigation.js'
export function ProgramScopeBar() {
+ const location = useLocation()
const {
initiativeId,
projectId,
@@ -12,12 +17,17 @@ export function ProgramScopeBar() {
setScope,
} = useProgramScope()
+ const portfolioTo = resolvePortfolioHub(location.pathname)
+ const initiativeTo = initiativeId
+ ? resolveInitiativeHub(location.pathname, initiativeId, scopedPath)
+ : null
+
return (
Scope
diff --git a/frontend/src/utils/scopeNavigation.js b/frontend/src/utils/scopeNavigation.js
new file mode 100644
index 0000000..234280d
--- /dev/null
+++ b/frontend/src/utils/scopeNavigation.js
@@ -0,0 +1,91 @@
+/**
+ * Modus-sensitive Scope-Navigation (AP1.9b).
+ * Breadcrumb-Ziele hängen vom aktiven Modus / Pfad ab — nicht immer Kontrolle.
+ */
+
+const MODE_DEFAULTS = {
+ cockpit: { portfolioHub: '/cockpit', initiativeHub: '/cockpit' },
+ work: { portfolioHub: '/work/today', initiativeHub: '/work/today' },
+ plan: { portfolioHub: '/plan/structure', initiativeHub: '/plan/structure' },
+ control: { portfolioHub: '/control/status', initiativeHub: '/control/status' },
+ team: { portfolioHub: '/team', initiativeHub: '/team' },
+}
+
+/**
+ * @param {string} pathname
+ * @returns {'cockpit'|'work'|'plan'|'control'|'team'}
+ */
+export function resolveAppMode(pathname) {
+ const path = pathname || '/'
+ if (path.startsWith('/actions') || path.startsWith('/work')) return 'work'
+ if (path.startsWith('/projects') || path.startsWith('/gates') || path.startsWith('/plan')) {
+ return 'plan'
+ }
+ if (path.startsWith('/control')) return 'control'
+ if (path.startsWith('/team')) return 'team'
+ if (path.startsWith('/cockpit') || path.startsWith('/workspace')) return 'cockpit'
+ if (path.startsWith('/initiatives')) return 'control'
+ return 'cockpit'
+}
+
+/**
+ * Basis-Pfad für Portfolio-Klick (Scope leeren, Modus beibehalten).
+ * @param {string} pathname
+ */
+export function resolvePortfolioHub(pathname) {
+ const mode = resolveAppMode(pathname)
+ const defaults = MODE_DEFAULTS[mode]
+
+ if (mode === 'plan' && pathname.startsWith('/plan/')) {
+ const sub = pathname.match(/^(\/plan\/[^/?]+)/)
+ if (sub) return sub[1]
+ }
+ if (mode === 'control' && pathname.startsWith('/control/')) {
+ const sub = pathname.match(/^(\/control\/[^/?]+)/)
+ if (sub) return sub[1]
+ }
+ if (mode === 'work' && pathname.startsWith('/work/')) {
+ const sub = pathname.match(/^(\/work\/[^/?]+)/)
+ if (sub) return sub[1]
+ }
+
+ return defaults.portfolioHub
+}
+
+/**
+ * Ziel für Klick auf Vorhaben im Breadcrumb (Scope behält Initiative, Projekt-Filter weg).
+ * @param {string} pathname
+ * @param {string} initiativeId
+ * @param {import('./routes.js').scopedPath} scopedPathFn
+ */
+export function resolveInitiativeHub(pathname, initiativeId, scopedPathFn) {
+ const mode = resolveAppMode(pathname)
+ const scope = { initiativeId }
+
+ if (mode === 'plan') {
+ if (pathname.startsWith('/gates/')) {
+ return scopedPathFn('/plan/gates', scope)
+ }
+ if (pathname.startsWith('/projects/')) {
+ return scopedPathFn('/plan/structure', scope)
+ }
+ const sub = pathname.match(/^(\/plan\/[^/?]+)/)
+ return scopedPathFn(sub ? sub[1] : MODE_DEFAULTS.plan.initiativeHub, scope)
+ }
+
+ if (mode === 'work' || pathname.startsWith('/actions/')) {
+ const sub = pathname.match(/^(\/work\/[^/?]+)/)
+ return scopedPathFn(sub ? sub[1] : MODE_DEFAULTS.work.initiativeHub, scope)
+ }
+
+ if (mode === 'control') {
+ const sub = pathname.match(/^(\/control\/[^/?]+)/)
+ return scopedPathFn(sub ? sub[1] : MODE_DEFAULTS.control.initiativeHub, scope)
+ }
+
+ if (mode === 'team') {
+ return scopedPathFn(MODE_DEFAULTS.team.initiativeHub, scope)
+ }
+
+ return scopedPathFn(MODE_DEFAULTS.cockpit.initiativeHub, scope)
+}
diff --git a/frontend/src/utils/scopeNavigation.test.js b/frontend/src/utils/scopeNavigation.test.js
new file mode 100644
index 0000000..959e677
--- /dev/null
+++ b/frontend/src/utils/scopeNavigation.test.js
@@ -0,0 +1,46 @@
+import { describe, expect, it } from 'vitest'
+import { scopedPath } from './routes.js'
+import {
+ resolveAppMode,
+ resolveInitiativeHub,
+ resolvePortfolioHub,
+} from './scopeNavigation.js'
+
+describe('scopeNavigation', () => {
+ it('resolves app mode from path', () => {
+ expect(resolveAppMode('/plan/structure')).toBe('plan')
+ expect(resolveAppMode('/plan/gates')).toBe('plan')
+ expect(resolveAppMode('/projects/abc')).toBe('plan')
+ expect(resolveAppMode('/gates/abc')).toBe('plan')
+ expect(resolveAppMode('/work/today')).toBe('work')
+ expect(resolveAppMode('/actions/abc')).toBe('work')
+ expect(resolveAppMode('/control/status')).toBe('control')
+ expect(resolveAppMode('/cockpit')).toBe('cockpit')
+ expect(resolveAppMode('/team')).toBe('team')
+ })
+
+ it('keeps plan sub-route for portfolio hub', () => {
+ expect(resolvePortfolioHub('/plan/gates')).toBe('/plan/gates')
+ expect(resolvePortfolioHub('/plan/inbox')).toBe('/plan/inbox')
+ })
+
+ it('initiative hub stays in plan mode on gates sub-route', () => {
+ const href = resolveInitiativeHub('/plan/gates', 'init-1', scopedPath)
+ expect(href).toBe('/plan/gates?initiative=init-1')
+ })
+
+ it('initiative hub from action object routes to work', () => {
+ const href = resolveInitiativeHub('/actions/act-1', 'init-1', scopedPath)
+ expect(href).toBe('/work/today?initiative=init-1')
+ })
+
+ it('initiative hub from project object routes to plan structure', () => {
+ const href = resolveInitiativeHub('/projects/proj-1', 'init-1', scopedPath)
+ expect(href).toBe('/plan/structure?initiative=init-1')
+ })
+
+ it('control initiative hub uses current sub-route', () => {
+ const href = resolveInitiativeHub('/control/journey', 'init-1', scopedPath)
+ expect(href).toBe('/control/journey?initiative=init-1')
+ })
+})