From 97fab2c76d66b093854e8610a4cee2f445d5359c Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 26 Jul 2026 16:52:48 +0200 Subject: [PATCH] docs: Archetyp-gesteuerte UI-Navigation verankern ARCHETYPE_UI_NAVIGATION_v0.1 plus Verweise in Spec-Template, README und Programm-Dokument. --- ...ro_Archetype_Specification_Program_v0.1.md | 1 + .../ARCHETYPE_UI_NAVIGATION_v0.1.md | 143 ++++++++++++++++++ docs/product/archetypes/README.md | 3 +- .../_TEMPLATE_Archetype_Spec_v0.1.md | 15 +- frontend/src/hooks/useMethodUiContext.js | 58 ++----- frontend/src/registry/OperatingRouteGate.jsx | 5 + 6 files changed, 175 insertions(+), 50 deletions(-) create mode 100644 docs/product/archetypes/ARCHETYPE_UI_NAVIGATION_v0.1.md diff --git a/docs/product/Kairo_Archetype_Specification_Program_v0.1.md b/docs/product/Kairo_Archetype_Specification_Program_v0.1.md index 7dcf4af..6de84d1 100644 --- a/docs/product/Kairo_Archetype_Specification_Program_v0.1.md +++ b/docs/product/Kairo_Archetype_Specification_Program_v0.1.md @@ -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 | | 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 | | Auftrag | Sprint Assignments | Scope + DoD für ein AP | | Ist | Truth Table | Living Check | diff --git a/docs/product/archetypes/ARCHETYPE_UI_NAVIGATION_v0.1.md b/docs/product/archetypes/ARCHETYPE_UI_NAVIGATION_v0.1.md new file mode 100644 index 0000000..6875943 --- /dev/null +++ b/docs/product/archetypes/ARCHETYPE_UI_NAVIGATION_v0.1.md @@ -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` | diff --git a/docs/product/archetypes/README.md b/docs/product/archetypes/README.md index c8b3dc5..04ac22c 100644 --- a/docs/product/archetypes/README.md +++ b/docs/product/archetypes/README.md @@ -3,7 +3,8 @@ **Programm:** [`VOLLSPEC_PROGRAM_v0.1.md`](VOLLSPEC_PROGRAM_v0.1.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) -**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. diff --git a/docs/product/archetypes/_TEMPLATE_Archetype_Spec_v0.1.md b/docs/product/archetypes/_TEMPLATE_Archetype_Spec_v0.1.md index da7c6de..5d66977 100644 --- a/docs/product/archetypes/_TEMPLATE_Archetype_Spec_v0.1.md +++ b/docs/product/archetypes/_TEMPLATE_Archetype_Spec_v0.1.md @@ -99,14 +99,19 @@ Filter: `compatible_archetype_keys` + `data_slices ⊆ om_capabilities` (siehe A ## 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 | |--------|---------| -| Prozessleiste | aus `ui_profile` (Archetyp) | -| Plan-Default | | -| Work-Default | | -| Control-Default | | +| `planOutlineKeys` | Sichtbare Plan-Sidebar (Reihenfolge = Array) | +| `workNavKeys` | Ausführen-Navigation | +| Prozessleiste | `processSteps` aus `ui_profile` (Archetyp) | +| 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 | -| Versteckt / sekundär | | +| Versteckt / sekundär | Key in Outline weglassen, nicht per If verstecken | Product Language: … diff --git a/frontend/src/hooks/useMethodUiContext.js b/frontend/src/hooks/useMethodUiContext.js index a251aab..aad0297 100644 --- a/frontend/src/hooks/useMethodUiContext.js +++ b/frontend/src/hooks/useMethodUiContext.js @@ -1,61 +1,31 @@ -import { useEffect, useMemo, useState } from 'react' -import { getInitiativeOperatingContext } from '../api/initiatives.js' -import { getActiveWorkCycle } from '../api/workCycles.js' +import { useMemo } from 'react' import { useProgramScope } from '../context/ProgramScopeContext.jsx' +import { useOptionalInitiativeOperations } from '../context/InitiativeOperationsContext.jsx' 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() { const { initiativeId } = useProgramScope() - const [operatingContext, setOperatingContext] = useState(null) - const [hasActiveSprint, setHasActiveSprint] = useState(false) - const [loading, setLoading] = useState(false) + const ops = useOptionalInitiativeOperations() - useEffect(() => { - if (!initiativeId) { - setOperatingContext(null) - 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 operatingContext = ops?.operatingContext ?? null + const hasActiveSprint = Boolean(ops?.activeWorkCycle?.id) + const loading = Boolean(initiativeId && ops?.loading) const profileInput = useMemo( () => ({ operatingContext, hasActiveSprint }), [operatingContext, hasActiveSprint], ) - const profile = useMemo( - () => resolveOperatingProfileFromInput(profileInput), - [profileInput], - ) + const profile = useMemo(() => { + if (ops?.operatingProfile) { + return ops.operatingProfile + } + return resolveOperatingProfileFromInput(profileInput) + }, [ops?.operatingProfile, profileInput]) return { initiativeId, diff --git a/frontend/src/registry/OperatingRouteGate.jsx b/frontend/src/registry/OperatingRouteGate.jsx index 37a152c..a38b427 100644 --- a/frontend/src/registry/OperatingRouteGate.jsx +++ b/frontend/src/registry/OperatingRouteGate.jsx @@ -18,6 +18,11 @@ export function OperatingRouteGate({ children }) { return children } + // Context noch nicht da (API-Fehler) → nicht fälschlich auf Profil umleiten + if (!operatingContext) { + return children + } + const input = { operatingContext } const path = location.pathname