diff --git a/backend/version.py b/backend/version.py
index 35e2d0e..8aaa9ed 100644
--- a/backend/version.py
+++ b/backend/version.py
@@ -1,3 +1,3 @@
-APP_VERSION = "0.16.4-ap1.9b"
+APP_VERSION = "0.16.5-ap1.12a"
DB_SCHEMA_VERSION = "014"
APP_NAME = "jinkendo-kairo"
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 8be2470..93b8a38 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -68,6 +68,8 @@ function AppRoutes() {
}>
} />
+ } />
+ } />
} />
} />
} />
diff --git a/frontend/src/components/PlanOutlineNav.jsx b/frontend/src/components/PlanOutlineNav.jsx
new file mode 100644
index 0000000..7789bb9
--- /dev/null
+++ b/frontend/src/components/PlanOutlineNav.jsx
@@ -0,0 +1,68 @@
+import { NavLink, useLocation } from 'react-router-dom'
+import { useProgramScope } from '../context/ProgramScopeContext.jsx'
+import {
+ PLAN_OUTLINE_NODES,
+ resolvePlanOutlineActiveKey,
+} from '../plan/planOutlineNodes.js'
+
+export function PlanOutlineNav() {
+ const location = useLocation()
+ const { initiativeId, initiativeTitle, hrefWithScope } = useProgramScope()
+ const activeKey = resolvePlanOutlineActiveKey(location.pathname)
+
+ return (
+
+ )
+}
diff --git a/frontend/src/pages/modes/PlanIndexRedirect.jsx b/frontend/src/pages/modes/PlanIndexRedirect.jsx
new file mode 100644
index 0000000..3a6d855
--- /dev/null
+++ b/frontend/src/pages/modes/PlanIndexRedirect.jsx
@@ -0,0 +1,13 @@
+import { Navigate } from 'react-router-dom'
+import { useProgramScope } from '../../context/ProgramScopeContext.jsx'
+
+/** Portfolio ohne Scope → Übersicht; mit Vorhaben → Profil als Einstieg. */
+export function PlanIndexRedirect() {
+ const { initiativeId, hrefWithScope } = useProgramScope()
+
+ if (initiativeId) {
+ return
+ }
+
+ return
+}
diff --git a/frontend/src/pages/modes/PlanLayout.jsx b/frontend/src/pages/modes/PlanLayout.jsx
index 024afa4..0b7c9fa 100644
--- a/frontend/src/pages/modes/PlanLayout.jsx
+++ b/frontend/src/pages/modes/PlanLayout.jsx
@@ -1,23 +1,19 @@
-import { Navigate, Outlet } from 'react-router-dom'
+import { Outlet } from 'react-router-dom'
import { ModeShell } from '../../components/ModeShell.jsx'
-import { ModeSubNav } from '../../components/ModeSubNav.jsx'
-
-const PLAN_NAV = [
- { to: '/plan/structure', label: 'Struktur', end: true },
- { to: '/plan/gates', label: 'Zielzustände', end: true },
- { to: '/plan/inbox', label: 'Eingang', end: true },
-]
+import { PlanOutlineNav } from '../../components/PlanOutlineNav.jsx'
export function PlanLayout() {
return (
-
+
)
}
-export function PlanIndexRedirect() {
- return
-}
+export { PlanIndexRedirect } from './PlanIndexRedirect.jsx'
diff --git a/frontend/src/pages/modes/PlanPortfolioPage.jsx b/frontend/src/pages/modes/PlanPortfolioPage.jsx
new file mode 100644
index 0000000..f554fbe
--- /dev/null
+++ b/frontend/src/pages/modes/PlanPortfolioPage.jsx
@@ -0,0 +1,64 @@
+import { Link } from 'react-router-dom'
+import { useEffect, useState } from 'react'
+import { listInitiatives } from '../../api/initiatives.js'
+import { useProgramScope } from '../../context/ProgramScopeContext.jsx'
+import { LoadingState } from '../../components/LoadingState.jsx'
+import { scopedPath } from '../../utils/routes.js'
+
+export function PlanPortfolioPage() {
+ const { applyFromInitiative } = useProgramScope()
+ const [initiatives, setInitiatives] = useState(null)
+
+ useEffect(() => {
+ let cancelled = false
+ listInitiatives()
+ .then((data) => {
+ if (!cancelled) setInitiatives(data)
+ })
+ .catch(() => {
+ if (!cancelled) setInitiatives([])
+ })
+ return () => {
+ cancelled = true
+ }
+ }, [])
+
+ if (initiatives === null) {
+ return
+ }
+
+ return (
+
+ Programm planen
+
+ Wähle ein Vorhaben im Scope oben oder in der Liste — dann erscheint die Plan-Outline
+ mit Profil, Struktur, Zielzuständen und Eingang.
+
+ {initiatives.length === 0 ? (
+
+ Noch keine Vorhaben angelegt.{' '}
+ Zum Vorhaben-Katalog
+
+ ) : (
+ <>
+
+ Schnellstart — Vorhaben öffnen:
+
+
+ {initiatives.map((item) => (
+ -
+ applyFromInitiative(item.id)}
+ >
+ {item.title}
+
+
+ ))}
+
+ >
+ )}
+
+ )
+}
diff --git a/frontend/src/pages/modes/PlanProfilePage.jsx b/frontend/src/pages/modes/PlanProfilePage.jsx
new file mode 100644
index 0000000..81bea34
--- /dev/null
+++ b/frontend/src/pages/modes/PlanProfilePage.jsx
@@ -0,0 +1,51 @@
+import { RequireInitiativeScope } from '../../components/RequireInitiativeScope.jsx'
+import { ScopedInitiativeProvider } from '../../layout/ScopedInitiativeProvider.jsx'
+import { StatusBadge } from '../../components/StatusBadge.jsx'
+import { LoadingState } from '../../components/LoadingState.jsx'
+import { useInitiativeOperations } from '../../context/InitiativeOperationsContext.jsx'
+
+function PlanProfileInner() {
+ const ops = useInitiativeOperations()
+ const { initiative, loading, error } = ops
+
+ if (loading) {
+ return
+ }
+
+ if (!initiative) {
+ return
Vorhaben nicht gefunden.
+ }
+
+ return (
+
+
+ {error && {error}
}
+ {initiative.goal ? (
+
+
Ziel
+
{initiative.goal}
+
+ ) : (
+
+ Noch kein Ziel hinterlegt — vollständiges Profil folgt mit Archetypen (AP1.10).
+
+ )}
+
+ Bearbeitung per Modal kommt in AP1.10 (Entity Field System).
+
+
+ )
+}
+
+export function PlanProfilePage() {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/plan/planOutlineNodes.js b/frontend/src/plan/planOutlineNodes.js
new file mode 100644
index 0000000..f7a4c08
--- /dev/null
+++ b/frontend/src/plan/planOutlineNodes.js
@@ -0,0 +1,34 @@
+/**
+ * Top-level Plan-Outline-Knoten (AP1.12a).
+ * Unterknoten (Projekt-Baum, Gate-Graph) folgen in AP1.12b+.
+ *
+ * @typedef {{ key: string, to: string, label: string, requiresInitiative?: boolean, disabled?: boolean, hint?: string }} PlanOutlineNode
+ */
+
+/** @type {PlanOutlineNode[]} */
+export const PLAN_OUTLINE_NODES = [
+ { key: 'profile', to: '/plan/profile', label: 'Profil', requiresInitiative: true },
+ { key: 'structure', to: '/plan/structure', label: 'Struktur', requiresInitiative: true },
+ { key: 'gates', to: '/plan/gates', label: 'Zielzustände', requiresInitiative: true },
+ { key: 'inbox', to: '/plan/inbox', label: 'Eingang', requiresInitiative: true },
+ {
+ key: 'work',
+ to: '/plan/work',
+ label: 'Arbeit',
+ requiresInitiative: true,
+ disabled: true,
+ hint: 'AP1.12 — Actions im Plan-Kontext',
+ },
+]
+
+/**
+ * @param {string} pathname
+ * @returns {string | null}
+ */
+export function resolvePlanOutlineActiveKey(pathname) {
+ const path = pathname.replace(/\/$/, '') || pathname
+ const match = PLAN_OUTLINE_NODES.find(
+ (node) => path === node.to || path.startsWith(`${node.to}/`),
+ )
+ return match?.key ?? null
+}
diff --git a/frontend/src/plan/planOutlineNodes.test.js b/frontend/src/plan/planOutlineNodes.test.js
new file mode 100644
index 0000000..e3dc553
--- /dev/null
+++ b/frontend/src/plan/planOutlineNodes.test.js
@@ -0,0 +1,22 @@
+import { describe, expect, it } from 'vitest'
+import { PLAN_OUTLINE_NODES, resolvePlanOutlineActiveKey } from './planOutlineNodes.js'
+
+describe('planOutlineNodes', () => {
+ it('lists plan sections in product order', () => {
+ expect(PLAN_OUTLINE_NODES.map((n) => n.key)).toEqual([
+ 'profile',
+ 'structure',
+ 'gates',
+ 'inbox',
+ 'work',
+ ])
+ })
+
+ it('resolves active outline key from pathname', () => {
+ expect(resolvePlanOutlineActiveKey('/plan/profile')).toBe('profile')
+ expect(resolvePlanOutlineActiveKey('/plan/structure')).toBe('structure')
+ expect(resolvePlanOutlineActiveKey('/plan/gates')).toBe('gates')
+ expect(resolvePlanOutlineActiveKey('/plan/inbox')).toBe('inbox')
+ expect(resolvePlanOutlineActiveKey('/plan/portfolio')).toBe(null)
+ })
+})
diff --git a/frontend/src/registry/viewRegistry.js b/frontend/src/registry/viewRegistry.js
index c4c4467..12d56f2 100644
--- a/frontend/src/registry/viewRegistry.js
+++ b/frontend/src/registry/viewRegistry.js
@@ -6,6 +6,8 @@ import { PlanLayout, PlanIndexRedirect } from '../pages/modes/PlanLayout.jsx'
import { PlanStructurePage } from '../pages/modes/PlanStructurePage.jsx'
import { PlanGatesPage } from '../pages/modes/PlanGatesPage.jsx'
import { PlanInboxPage } from '../pages/modes/PlanInboxPage.jsx'
+import { PlanProfilePage } from '../pages/modes/PlanProfilePage.jsx'
+import { PlanPortfolioPage } from '../pages/modes/PlanPortfolioPage.jsx'
import { ControlLayout, ControlIndexRedirect } from '../pages/modes/ControlLayout.jsx'
import { ControlStatusPage } from '../pages/modes/ControlStatusPage.jsx'
import { ControlJourneyPage } from '../pages/modes/ControlJourneyPage.jsx'
@@ -125,6 +127,8 @@ export function getViewByKey(key) {
export const MODE_ROUTE_COMPONENTS = {
workToday: WorkTodayPage,
workMine: WorkMinePage,
+ planPortfolio: PlanPortfolioPage,
+ planProfile: PlanProfilePage,
planStructure: PlanStructurePage,
planGates: PlanGatesPage,
planInbox: PlanInboxPage,
diff --git a/frontend/src/styles/program-chrome.css b/frontend/src/styles/program-chrome.css
index 7239c28..5c04fa2 100644
--- a/frontend/src/styles/program-chrome.css
+++ b/frontend/src/styles/program-chrome.css
@@ -291,3 +291,122 @@
.section-header--compact {
margin-bottom: 12px;
}
+
+.plan-outline-shell__body {
+ display: grid;
+ grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
+ gap: 20px;
+ align-items: start;
+}
+
+@media (max-width: 900px) {
+ .plan-outline-shell__body {
+ grid-template-columns: 1fr;
+ }
+}
+
+.plan-outline-nav {
+ position: sticky;
+ top: 12px;
+ padding: 14px;
+ border-radius: 12px;
+ border: 1px solid var(--jk-border);
+ background: var(--jk-surface);
+}
+
+.plan-outline-nav__header {
+ margin-bottom: 12px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid var(--jk-border);
+}
+
+.plan-outline-nav__eyebrow {
+ display: block;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ color: var(--jk-text-muted);
+}
+
+.plan-outline-nav__root {
+ margin: 6px 0 0;
+ font-size: 15px;
+ font-weight: 700;
+ line-height: 1.3;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.plan-outline-nav__root--muted {
+ font-weight: 600;
+ color: var(--jk-text-muted);
+}
+
+.plan-outline-nav__list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: grid;
+ gap: 4px;
+}
+
+.plan-outline-nav__link {
+ display: block;
+ padding: 8px 10px;
+ border-radius: 8px;
+ text-decoration: none;
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--jk-text-muted);
+}
+
+.plan-outline-nav__link:hover {
+ background: var(--jk-primary-soft);
+ color: var(--jk-text);
+}
+
+.plan-outline-nav__link--active {
+ color: var(--jk-primary);
+ background: var(--jk-primary-soft);
+}
+
+.plan-outline-nav__link--disabled {
+ opacity: 0.55;
+ cursor: not-allowed;
+}
+
+.plan-outline-shell__content {
+ min-width: 0;
+}
+
+.plan-profile__header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ flex-wrap: wrap;
+ margin-bottom: 12px;
+}
+
+.plan-profile__label {
+ margin: 0 0 4px;
+ font-size: 12px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ color: var(--jk-text-muted);
+}
+
+.plan-profile__field {
+ margin-top: 12px;
+}
+
+.plan-profile__note {
+ margin-top: 16px;
+ font-size: 13px;
+}
+
+.plan-portfolio__hint {
+ margin-top: 8px;
+}
diff --git a/frontend/src/utils/scopeNavigation.js b/frontend/src/utils/scopeNavigation.js
index 234280d..e2822a8 100644
--- a/frontend/src/utils/scopeNavigation.js
+++ b/frontend/src/utils/scopeNavigation.js
@@ -6,7 +6,7 @@
const MODE_DEFAULTS = {
cockpit: { portfolioHub: '/cockpit', initiativeHub: '/cockpit' },
work: { portfolioHub: '/work/today', initiativeHub: '/work/today' },
- plan: { portfolioHub: '/plan/structure', initiativeHub: '/plan/structure' },
+ plan: { portfolioHub: '/plan/portfolio', initiativeHub: '/plan/profile' },
control: { portfolioHub: '/control/status', initiativeHub: '/control/status' },
team: { portfolioHub: '/team', initiativeHub: '/team' },
}