docs: Archetyp-gesteuerte UI-Navigation verankern
Some checks failed
Deploy Development / deploy (push) Successful in 50s
Test Suite / pytest-backend (push) Failing after 3m50s
Test Suite / k6 /api/health Baseline (push) Has been skipped
Test Suite / playwright-smoke (push) Has been skipped
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped

ARCHETYPE_UI_NAVIGATION_v0.1 plus Verweise in Spec-Template, README und Programm-Dokument.
This commit is contained in:
Lars 2026-07-26 16:52:48 +02:00
parent 11b7128204
commit 97fab2c76d
6 changed files with 175 additions and 50 deletions

View File

@ -41,6 +41,7 @@ Kairo_Implementation_Truth_Table_v0.1.md ← Was ist wirklich da?
|---------|----------|--------| |---------|----------|--------|
| Katalog | ADP v0.2 §3§5 | Inventar, Nutzerbild, Scope Lock | | Katalog | ADP v0.2 §3§5 | Inventar, Nutzerbild, Scope Lock |
| Plugin | ADP AP2.3 / AP2.4 | Archetyp ↔ Methode, Element-Registry, Operating Context | | Plugin | ADP AP2.3 / AP2.4 | Archetyp ↔ Methode, Element-Registry, Operating Context |
| UI-Navigation | `docs/product/archetypes/ARCHETYPE_UI_NAVIGATION_v0.1.md` | Nav, Outline, Default-Routen, Slice-Gating — Spec-§6 |
| Spec | `docs/product/archetypes/` | Implementierbare Detail-Spec | | Spec | `docs/product/archetypes/` | Implementierbare Detail-Spec |
| Auftrag | Sprint Assignments | Scope + DoD für ein AP | | Auftrag | Sprint Assignments | Scope + DoD für ein AP |
| Ist | Truth Table | Living Check | | Ist | Truth Table | Living Check |

View File

@ -0,0 +1,143 @@
# Archetyp-gesteuerte UI-Navigation v0.1
**Status:** verbindlich für Specs und Implementierung
**Stand:** 2026-07-26
**Technische Guardrails:** `.cursor/rules/kairo-plugin-architecture.mdc`
**Code-Seeds:** `backend/entity_archetypes/ui_profiles.py``ui_profile_json` (DB)
---
## 1. Leitprinzip
**Jeder Vorhabentyp (Archetyp) definiert seine eigene Navigations-Hülle** — welche Modi, Outline-Punkte, Default-Routen und Prozessschritte der Nutzer sieht.
Die **Steuerungsmethode** schärft danach, welche OM-Daten und UI-Bausteine *tatsächlich* aktiv sind — nicht umgekehrt.
```text
Sichtbarkeit (Nav, Outline) → Archetyp ui_profile
Erreichbarkeit (Route Gating) → data_slices = om_capabilities ∩ method.data_slices
Verhalten (Panels, Strategien) → method.ui_features + steering_elements
```
**Keine Archetyp-Ifs in React-Pages.** Specs beschreiben `ui_profile`-Felder; Code liest `GET /api/initiatives/:id/operating-context`.
---
## 2. Schichten zur Laufzeit
```mermaid
flowchart TB
A[Archetyp ui_profile] --> OC[Operating Context API]
M[Methode MethodDefinition] --> OC
OC --> R[resolveOperatingProfile.js]
R --> NAV[PlanOutlineNav / WorkNav / ProcessStrip]
R --> GATE[OperatingRouteGate]
R --> OPS[InitiativeOperationsContext / loadOperatingSlices]
M --> STEER[steering/kernel evaluate_steering]
```
| Schicht | Quelle | Steuert |
|---------|--------|---------|
| **Archetyp** | `ui_profile_json` | Plan-Outline, Work-Nav, Prozessleiste, Default-Routen, `dataSlices` / `om_capabilities` |
| **Methode** | `steering/methods/registrations/` | Effektive `data_slices`, `ui_features`, `steering_elements`, Graph-Profil, Next-Action-Strategie |
| **Runtime** | Operating Context + Steering Kernel | Aufgelöstes Profil pro Initiative |
Archetyp-Default-Methode ist **Empfehlung** bei Anlage — kompatible Methoden werden über `compatible_archetype_keys` und `om_capabilities` gefiltert.
---
## 3. `ui_profile`-Felder (Archetyp-Spec §6)
Diese Felder gehören in jede Archetyp-Spec und in `INITIATIVE_UI_PROFILES` / DB-Seed:
| Feld | Wirkung | Beispiel Product |
|------|---------|------------------|
| `planOutlineKeys` | Sichtbare Plan-Sidebar-Knoten (Reihenfolge = Array-Reihenfolge) | `profile`, `inbox`, `sprint`, `gates` |
| `workNavKeys` | Ausführen-Navigation | `sprint`, `today`, `mine` |
| `processSteps` | Prozessleiste (Quer-Navigation zwischen Modi) | Eingang → Sprint planen → Ausführen → Kontrolle |
| `planDefaultRoute` | Redirect bei `/plan` mit Vorhaben-Scope | `/plan/inbox` |
| `workDefaultRoute` | Redirect bei `/work` mit Scope | `/work/sprint` |
| `workDefaultRouteWithoutActiveWorkCycle` | Work-Default ohne aktiven Sprint | `/work/today` |
| `controlDefaultRoute` | Redirect bei `/control` mit Scope | `/control/status` |
| `dataSlices` | OM-Fähigkeiten des Archetyps (Superset) | `backlog`, `roadmap`, `work_cycles`, … |
| `enabledModes` | Optionale Modus-Einschränkung | `plan`, `work`, `control` |
**Plan-Outline-Keys** (Referenz): `profile`, `structure`, `gates`, `inbox`, `sprint`, `work` — siehe `frontend/src/plan/planOutlineNodes.js`.
**Nicht am Archetyp:** `ui_features` und `steering_elements` — ausschließlich Methoden-Vertrag (AP2.4).
---
## 4. Route Gating und Slice-Loader
Modus-Routen deklarieren `requiredSlices` in `frontend/src/registry/viewRegistry.js` (`MODE_ROUTE_GROUPS`):
| Route | `requiredSlices` |
|-------|------------------|
| `/plan/inbox` | `backlog` |
| `/plan/gates` | `roadmap` |
| `/plan/sprint` | `work_cycles` |
| `/plan/structure` | `projects` |
| `/work/today`, `/work/mine` | `actions` |
`OperatingRouteGate` erlaubt eine Route nur, wenn alle `requiredSlices` in den **effektiven** `data_slices` enthalten sind.
Effektive Slices (Backend):
```text
data_slices = om_capabilities ∩ method.data_slices
om_capabilities = ui_profile.omCapabilities || ui_profile.dataSlices
```
Frontend-Fallback (2026-07-26): Ist `data_slices` leer, nutzt `resolveEffectiveDataSlices()` die `ui_profile.dataSlices` — Nav und Gating dürfen nicht auseinanderlaufen.
Datenladen: `InitiativeOperationsContext` lädt OM-Slices anhand derselben effektiven Liste (`loadOperatingSlices`).
---
## 5. Was Spec-Autoren liefern
In **§6 UI / IA** jeder Archetyp-Spec:
1. **Plan-Outline** — welche `planOutlineKeys`, in welcher Reihenfolge; was bewusst fehlt (z. B. Dispute Case ohne Gates)
2. **Default-Routen** — wohin bei Scope-Wechsel und bei blockierter Route
3. **Prozessleiste**`processSteps` mit Label und Ziel-Route
4. **OM-Slices** — vollständige `dataSlices`-Liste (muss zu Plan-/Work-Routen passen)
5. **Abgrenzung Methode** — welche `ui_features` / `steering_elements` die Default-Methode liefert (Verweis auf Methoden-Spec)
**Anti-Pattern in Specs:** „Seite X nur für Archetyp Y anzeigen“ ohne `ui_profile`-Feld — stattdessen Outline-Key weglassen oder Route über Slice-Gating absichern.
---
## 6. Erweiterung (Open/Closed)
| Änderung | Wo | Nicht |
|----------|-----|-------|
| Neuer Archetyp | `ui_profiles.py` + Registry-Seed + Spec §6 | React-If in Pages |
| Andere Nav für bestehenden Archetyp | `ui_profile_json` (DB-Sync) + Spec-Update | Hardcode in `PlanOutlineNav` |
| Neues UI-Panel / Steuerungsbaustein | Methoden-`steering_elements` + Element-Registry | Archetyp-Profil |
| Neue Plan-Route | `MODE_ROUTE_GROUPS` + `requiredSlices` + ggf. Outline-Node | Ad-hoc-Redirect in Page |
---
## 7. Abnahme-Checkliste (Archetyp-Slice)
- [ ] Operating Context liefert `ui_profile` mit Spec-konformen Keys und Routen
- [ ] `data_slices` enthält alle für den Archetyp deklarierten Slices, die die Methode unterstützt
- [ ] Plan-Outline zeigt nur Spec-Keys; Klick führt nicht zu Profil-Redirect
- [ ] Default-Routen (`/plan`, `/work`, `/control` mit Scope) entsprechen Spec
- [ ] Keine `archetype_key`-Ifs in betroffenen Pages
- [ ] Spec §11 (Ist-Stand) verweist auf Operating Context / Route Gating
---
## 8. Referenzen
| Artefakt | Pfad |
|----------|------|
| UI-Profile Seeds | `backend/entity_archetypes/ui_profiles.py` |
| Operating Context | `backend/services/operating_context.py` |
| FE Resolver | `frontend/src/registry/resolveOperatingProfile.js` |
| Route Gating | `frontend/src/registry/OperatingRouteGate.jsx`, `modeRouteRegistry.js` |
| Plugin-ADP | `docs/architecture/ADP_Archetype_Method_Plugin_Architecture_v0.1.md` |
| Spec-Template §6 | `docs/product/archetypes/_TEMPLATE_Archetype_Spec_v0.1.md` |

View File

@ -4,6 +4,7 @@
**Decision-Lock:** [`../Kairo_Archetype_Decision_Lock_Interview_2026-07-25.md`](../Kairo_Archetype_Decision_Lock_Interview_2026-07-25.md) **Decision-Lock:** [`../Kairo_Archetype_Decision_Lock_Interview_2026-07-25.md`](../Kairo_Archetype_Decision_Lock_Interview_2026-07-25.md)
**Methodenkern:** [`../../architecture/Kairo_Steering_Method_Kernel_v0.1.md`](../../architecture/Kairo_Steering_Method_Kernel_v0.1.md) **Methodenkern:** [`../../architecture/Kairo_Steering_Method_Kernel_v0.1.md`](../../architecture/Kairo_Steering_Method_Kernel_v0.1.md)
**Methoden Spec-D:** [`../../architecture/methods/README.md`](../../architecture/methods/README.md) **Methoden Spec-D:** [`../../architecture/methods/README.md`](../../architecture/methods/README.md)
**UI-Navigation (Archetyp):** [`ARCHETYPE_UI_NAVIGATION_v0.1.md`](ARCHETYPE_UI_NAVIGATION_v0.1.md)
**PO 2026-07-26:** Specs **vorläufig freigegeben** — Implementierungsausreichendheit noch nicht abschließend bewertet. **PO 2026-07-26:** Specs **vorläufig freigegeben** — Implementierungsausreichendheit noch nicht abschließend bewertet.

View File

@ -99,14 +99,19 @@ Filter: `compatible_archetype_keys` + `data_slices ⊆ om_capabilities` (siehe A
## 6. UI / IA ## 6. UI / IA
**Architektur:** [`ARCHETYPE_UI_NAVIGATION_v0.1.md`](ARCHETYPE_UI_NAVIGATION_v0.1.md) — Nav, Routen und Slice-Gating über `ui_profile` (Archetyp) + Operating Context. Keine Page-Ifs.
| Aspekt | Vorgabe | | Aspekt | Vorgabe |
|--------|---------| |--------|---------|
| Prozessleiste | aus `ui_profile` (Archetyp) | | `planOutlineKeys` | Sichtbare Plan-Sidebar (Reihenfolge = Array) |
| Plan-Default | | | `workNavKeys` | Ausführen-Navigation |
| Work-Default | | | Prozessleiste | `processSteps` aus `ui_profile` (Archetyp) |
| Control-Default | | | Plan-Default | `planDefaultRoute` |
| Work-Default | `workDefaultRoute` (+ ggf. `workDefaultRouteWithoutActiveWorkCycle`) |
| Control-Default | `controlDefaultRoute` |
| `dataSlices` | OM-Fähigkeiten — müssen zu Routen in §6 passen (`backlog` → Eingang, `roadmap` → Gates, …) |
| `ui_features` (Methode) | z. B. recurring_panel — **nicht** am Archetyp | | `ui_features` (Methode) | z. B. recurring_panel — **nicht** am Archetyp |
| Versteckt / sekundär | | | Versteckt / sekundär | Key in Outline weglassen, nicht per If verstecken |
Product Language: … Product Language: …

View File

@ -1,61 +1,31 @@
import { useEffect, useMemo, useState } from 'react' import { useMemo } from 'react'
import { getInitiativeOperatingContext } from '../api/initiatives.js'
import { getActiveWorkCycle } from '../api/workCycles.js'
import { useProgramScope } from '../context/ProgramScopeContext.jsx' import { useProgramScope } from '../context/ProgramScopeContext.jsx'
import { useOptionalInitiativeOperations } from '../context/InitiativeOperationsContext.jsx'
import { resolveOperatingProfileFromInput } from '../registry/resolveOperatingProfile.js' import { resolveOperatingProfileFromInput } from '../registry/resolveOperatingProfile.js'
/** /**
* Lädt Operating Context für methodengebundene UI (AP2.3b). * Methoden-/Archetyp-UI aus dem gemeinsamen InitiativeOperationsProvider (AP2.3b).
* Kein separater Fetch pro Komponente verhindert Route-Gate-Races beim Modus-Wechsel.
*/ */
export function useMethodUiContext() { export function useMethodUiContext() {
const { initiativeId } = useProgramScope() const { initiativeId } = useProgramScope()
const [operatingContext, setOperatingContext] = useState(null) const ops = useOptionalInitiativeOperations()
const [hasActiveSprint, setHasActiveSprint] = useState(false)
const [loading, setLoading] = useState(false)
useEffect(() => { const operatingContext = ops?.operatingContext ?? null
if (!initiativeId) { const hasActiveSprint = Boolean(ops?.activeWorkCycle?.id)
setOperatingContext(null) const loading = Boolean(initiativeId && ops?.loading)
setHasActiveSprint(false)
return undefined
}
let cancelled = false
setLoading(true)
Promise.all([
getInitiativeOperatingContext(initiativeId).catch(() => null),
getActiveWorkCycle(initiativeId).catch(() => null),
])
.then(([context, activeCycle]) => {
if (cancelled) return
setOperatingContext(context)
setHasActiveSprint(Boolean(activeCycle?.id))
})
.catch(() => {
if (!cancelled) {
setOperatingContext(null)
setHasActiveSprint(false)
}
})
.finally(() => {
if (!cancelled) setLoading(false)
})
return () => {
cancelled = true
}
}, [initiativeId])
const profileInput = useMemo( const profileInput = useMemo(
() => ({ operatingContext, hasActiveSprint }), () => ({ operatingContext, hasActiveSprint }),
[operatingContext, hasActiveSprint], [operatingContext, hasActiveSprint],
) )
const profile = useMemo( const profile = useMemo(() => {
() => resolveOperatingProfileFromInput(profileInput), if (ops?.operatingProfile) {
[profileInput], return ops.operatingProfile
) }
return resolveOperatingProfileFromInput(profileInput)
}, [ops?.operatingProfile, profileInput])
return { return {
initiativeId, initiativeId,

View File

@ -18,6 +18,11 @@ export function OperatingRouteGate({ children }) {
return children return children
} }
// Context noch nicht da (API-Fehler) nicht fälschlich auf Profil umleiten
if (!operatingContext) {
return children
}
const input = { operatingContext } const input = { operatingContext }
const path = location.pathname const path = location.pathname