diff --git a/backend/entity_archetypes/ui_profiles.py b/backend/entity_archetypes/ui_profiles.py
index 9d5d606..d33e2b6 100644
--- a/backend/entity_archetypes/ui_profiles.py
+++ b/backend/entity_archetypes/ui_profiles.py
@@ -124,7 +124,7 @@ INITIATIVE_UI_PROFILES: dict[str, dict[str, Any]] = {
"planDefaultRoute": "/plan/gates",
"workDefaultRoute": "/work/today",
"controlDefaultRoute": "/control/status",
- "planOutlineKeys": ["profile", "gates", "inbox", "work"],
+ "planOutlineKeys": ["profile", "gates", "work"],
"workNavKeys": ["today", "mine", "sprint"],
"dataSlices": [
"actions",
diff --git a/backend/migrations/030_cadence_instances.sql b/backend/migrations/030_cadence_instances.sql
new file mode 100644
index 0000000..02e7700
--- /dev/null
+++ b/backend/migrations/030_cadence_instances.sql
@@ -0,0 +1,25 @@
+-- AP A1.1 — CadenceInstance (minimal): eine offene Instanz pro Recurring, Tages-Tracking
+
+CREATE TABLE cadence_instances (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
+ recurring_element_id UUID NOT NULL REFERENCES recurring_elements(id) ON DELETE CASCADE,
+ status VARCHAR(32) NOT NULL DEFAULT 'open'
+ CHECK (status IN ('open', 'completed')),
+ due_at TIMESTAMPTZ NOT NULL,
+ completed_at TIMESTAMPTZ NULL,
+ measurement_note TEXT NOT NULL DEFAULT '',
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_cadence_instances_tenant_recurring
+ ON cadence_instances(tenant_id, recurring_element_id);
+
+CREATE INDEX idx_cadence_instances_open_due
+ ON cadence_instances(tenant_id, due_at)
+ WHERE status = 'open';
+
+CREATE UNIQUE INDEX idx_cadence_instances_one_open_per_recurring
+ ON cadence_instances(recurring_element_id)
+ WHERE status = 'open';
diff --git a/backend/routers/recurring.py b/backend/routers/recurring.py
index 5c40f06..a4768cd 100644
--- a/backend/routers/recurring.py
+++ b/backend/routers/recurring.py
@@ -24,6 +24,10 @@ class RecurringUpdateRequest(BaseModel):
clear_next_due_at: bool = False
+class RecurringCompleteRequest(BaseModel):
+ measurement_note: Optional[str] = Field(default=None, max_length=2000)
+
+
def _parse_datetime(value: Optional[str]) -> Optional[datetime]:
if value is None:
return None
@@ -73,6 +77,29 @@ def update_recurring_element(
return item
+@router.post("/{recurring_id}/complete")
+def complete_recurring_practice(
+ recurring_id: str,
+ body: RecurringCompleteRequest,
+ ctx: TenantContext = Depends(require_capability("kairo.recurring.manage")),
+):
+ from services.cadence_instance import complete_open_instance
+
+ try:
+ result = complete_open_instance(
+ tenant_id=ctx.tenant_id,
+ recurring_element_id=recurring_id,
+ user_id=ctx.user_id or None,
+ measurement_note=body.measurement_note or "",
+ )
+ except ValueError as exc:
+ detail = str(exc)
+ if detail == "Recurring-Element nicht gefunden":
+ raise HTTPException(status_code=404, detail=detail) from exc
+ raise HTTPException(status_code=400, detail=detail) from exc
+ return result
+
+
@router.delete("/{recurring_id}", status_code=204)
def delete_recurring_element(
recurring_id: str,
diff --git a/backend/services/cadence_instance.py b/backend/services/cadence_instance.py
new file mode 100644
index 0000000..1ba3cce
--- /dev/null
+++ b/backend/services/cadence_instance.py
@@ -0,0 +1,220 @@
+"""CadenceInstance service — AP A1.1 minimal daily practice tracking."""
+
+from __future__ import annotations
+
+from datetime import datetime, timedelta, timezone
+from typing import Any, Literal, Optional
+
+from psycopg2.extras import RealDictCursor
+
+from db import get_connection
+from services.audit import log_audit
+
+CadenceStatus = Literal["open", "completed"]
+
+_CADENCE_COLUMNS = """
+ id, tenant_id, recurring_element_id, status, due_at,
+ completed_at, measurement_note, created_at, updated_at
+"""
+
+
+def _serialize_row(row: dict[str, Any]) -> dict[str, Any]:
+ result = dict(row)
+ for key in ("id", "tenant_id", "recurring_element_id"):
+ if result.get(key):
+ result[key] = str(result[key])
+ for ts_key in ("due_at", "completed_at", "created_at", "updated_at"):
+ if result.get(ts_key):
+ result[ts_key] = result[ts_key].isoformat()
+ return result
+
+
+def get_open_instance(
+ *, tenant_id: str, recurring_element_id: str
+) -> Optional[dict[str, Any]]:
+ conn = get_connection()
+ try:
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
+ cur.execute(
+ f"""
+ SELECT {_CADENCE_COLUMNS}
+ FROM cadence_instances
+ WHERE tenant_id = %s
+ AND recurring_element_id = %s
+ AND status = 'open'
+ LIMIT 1
+ """,
+ (tenant_id, recurring_element_id),
+ )
+ row = cur.fetchone()
+ return _serialize_row(dict(row)) if row else None
+ finally:
+ conn.close()
+
+
+def ensure_open_instance(
+ *,
+ tenant_id: str,
+ recurring_element_id: str,
+ due_at: Optional[datetime] = None,
+) -> dict[str, Any]:
+ """Ensure exactly one open CadenceInstance (idempotent)."""
+ existing = get_open_instance(
+ tenant_id=tenant_id, recurring_element_id=recurring_element_id
+ )
+ if existing:
+ return existing
+
+ due = due_at or datetime.now(timezone.utc)
+ conn = get_connection()
+ try:
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
+ cur.execute(
+ f"""
+ INSERT INTO cadence_instances (
+ tenant_id, recurring_element_id, status, due_at
+ )
+ VALUES (%s, %s, 'open', %s)
+ RETURNING {_CADENCE_COLUMNS}
+ """,
+ (tenant_id, recurring_element_id, due),
+ )
+ row = _serialize_row(dict(cur.fetchone()))
+ conn.commit()
+ finally:
+ conn.close()
+ return row
+
+
+def complete_open_instance(
+ *,
+ tenant_id: str,
+ recurring_element_id: str,
+ user_id: Optional[str] = None,
+ measurement_note: str = "",
+) -> dict[str, Any]:
+ """
+ Close open instance, advance recurring next_due_at, open next instance.
+ Returns { completed, recurring, next_instance }.
+ """
+ from services.recurring import get_recurring_element, update_recurring_element
+
+ recurring = get_recurring_element(
+ tenant_id=tenant_id, recurring_id=recurring_element_id
+ )
+ if not recurring:
+ raise ValueError("Recurring-Element nicht gefunden")
+ if recurring["status"] != "active":
+ raise ValueError("Recurring-Element ist nicht aktiv")
+
+ open_inst = get_open_instance(
+ tenant_id=tenant_id, recurring_element_id=recurring_element_id
+ )
+ if not open_inst:
+ open_inst = ensure_open_instance(
+ tenant_id=tenant_id,
+ recurring_element_id=recurring_element_id,
+ due_at=datetime.now(timezone.utc),
+ )
+
+ now = datetime.now(timezone.utc)
+ note = (measurement_note or "").strip()
+
+ conn = get_connection()
+ try:
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
+ cur.execute(
+ f"""
+ UPDATE cadence_instances
+ SET status = 'completed',
+ completed_at = %s,
+ measurement_note = %s,
+ updated_at = NOW()
+ WHERE id = %s AND tenant_id = %s AND status = 'open'
+ RETURNING {_CADENCE_COLUMNS}
+ """,
+ (now, note, open_inst["id"], tenant_id),
+ )
+ completed_row = cur.fetchone()
+ if not completed_row:
+ raise ValueError("Keine offene Cadence-Instanz")
+ completed = _serialize_row(dict(completed_row))
+ conn.commit()
+ finally:
+ conn.close()
+
+ interval = recurring.get("interval_days") or 1
+ next_due = now + timedelta(days=int(interval))
+
+ update_recurring_element(
+ tenant_id=tenant_id,
+ recurring_id=recurring_element_id,
+ user_id=user_id,
+ next_due_at=next_due,
+ )
+
+ next_instance = ensure_open_instance(
+ tenant_id=tenant_id,
+ recurring_element_id=recurring_element_id,
+ due_at=next_due,
+ )
+
+ log_audit(
+ "cadence_instance.completed",
+ user_id=user_id,
+ tenant_id=tenant_id,
+ details={
+ "cadence_instance_id": completed["id"],
+ "recurring_id": recurring_element_id,
+ "measurement_note": note[:200] if note else "",
+ },
+ )
+
+ updated_recurring = get_recurring_element(
+ tenant_id=tenant_id, recurring_id=recurring_element_id
+ )
+ return {
+ "completed_instance": completed,
+ "next_instance": next_instance,
+ "recurring": updated_recurring,
+ }
+
+
+def attach_open_instances_to_recurring_list(
+ items: list[dict[str, Any]], *, tenant_id: str
+) -> list[dict[str, Any]]:
+ if not items:
+ return items
+ ids = [item["id"] for item in items]
+ conn = get_connection()
+ try:
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
+ cur.execute(
+ f"""
+ SELECT {_CADENCE_COLUMNS}
+ FROM cadence_instances
+ WHERE tenant_id = %s
+ AND recurring_element_id = ANY(%s::uuid[])
+ AND status = 'open'
+ """,
+ (tenant_id, ids),
+ )
+ by_recurring = {
+ str(row["recurring_element_id"]): _serialize_row(dict(row))
+ for row in cur.fetchall()
+ }
+ finally:
+ conn.close()
+
+ enriched = []
+ for item in items:
+ copy = dict(item)
+ open_inst = by_recurring.get(item["id"])
+ if not open_inst and item.get("status") == "active":
+ open_inst = ensure_open_instance(
+ tenant_id=tenant_id,
+ recurring_element_id=item["id"],
+ )
+ copy["open_cadence_instance"] = open_inst
+ enriched.append(copy)
+ return enriched
diff --git a/backend/services/recurring.py b/backend/services/recurring.py
index f429b98..2075d16 100644
--- a/backend/services/recurring.py
+++ b/backend/services/recurring.py
@@ -90,6 +90,16 @@ def create_recurring_element(
tenant_id=tenant_id,
details={"recurring_id": row["id"], "initiative_id": initiative_id},
)
+
+ from services.cadence_instance import ensure_open_instance
+
+ due = next_due_at or datetime.now(timezone.utc)
+ open_inst = ensure_open_instance(
+ tenant_id=tenant_id,
+ recurring_element_id=row["id"],
+ due_at=due,
+ )
+ row["open_cadence_instance"] = open_inst
return row
@@ -111,10 +121,14 @@ def list_recurring_for_initiative(
""",
(tenant_id, initiative_id),
)
- return [_serialize_row(dict(r)) for r in cur.fetchall()]
+ rows = [_serialize_row(dict(r)) for r in cur.fetchall()]
finally:
conn.close()
+ from services.cadence_instance import attach_open_instances_to_recurring_list
+
+ return attach_open_instances_to_recurring_list(rows, tenant_id=tenant_id)
+
def get_recurring_element(
*, tenant_id: str, recurring_id: str
diff --git a/backend/steering/strategies/next_action/recurring_helpers.py b/backend/steering/strategies/next_action/recurring_helpers.py
index b265cdc..acba6fc 100644
--- a/backend/steering/strategies/next_action/recurring_helpers.py
+++ b/backend/steering/strategies/next_action/recurring_helpers.py
@@ -22,10 +22,11 @@ def recurring_due_candidates(
re.title AS title,
'Wiederkehrendes Element fällig' AS summary,
re.initiative_id,
+ re.id AS recurring_id,
NULL::uuid AS action_id,
NULL::uuid AS backlog_item_id,
'recurring_due' AS reason_code,
- 'Rhythmus-Element bearbeiten' AS recommended_action
+ 'Übung heute erledigen' AS recommended_action
FROM recurring_elements re
WHERE re.tenant_id = %s AND re.initiative_id = %s
AND re.status = 'active'
@@ -40,6 +41,8 @@ def recurring_due_candidates(
for row in cur.fetchall():
item = dict(row)
item["initiative_id"] = str(item["initiative_id"])
+ if item.get("recurring_id"):
+ item["recurring_id"] = str(item["recurring_id"])
items.append(item)
return items
finally:
@@ -60,10 +63,11 @@ def active_recurring_candidates(
re.title AS title,
'Heutige Übung / Routine' AS summary,
re.initiative_id,
+ re.id AS recurring_id,
NULL::uuid AS action_id,
NULL::uuid AS backlog_item_id,
'recurring_active' AS reason_code,
- 'Routine ausführen' AS recommended_action
+ 'Übung heute erledigen' AS recommended_action
FROM recurring_elements re
WHERE re.tenant_id = %s AND re.initiative_id = %s
AND re.status = 'active'
@@ -76,6 +80,8 @@ def active_recurring_candidates(
for row in cur.fetchall():
item = dict(row)
item["initiative_id"] = str(item["initiative_id"])
+ if item.get("recurring_id"):
+ item["recurring_id"] = str(item["recurring_id"])
items.append(item)
return items
finally:
diff --git a/backend/tests/test_a1_cadence_mvp.py b/backend/tests/test_a1_cadence_mvp.py
new file mode 100644
index 0000000..10b6eee
--- /dev/null
+++ b/backend/tests/test_a1_cadence_mvp.py
@@ -0,0 +1,70 @@
+"""AP A1.1 — CadenceInstance complete practice flow."""
+
+from __future__ import annotations
+
+from tests.factories import provision_user_in_tenant
+from tests.test_initiatives_actions import _auth, _create_initiative, _login
+
+
+def test_complete_daily_practice_advances_cadence(client):
+ user = provision_user_in_tenant(tenant_role="admin")
+ token = _login(client, user)
+
+ created = _create_initiative(
+ client,
+ token,
+ title="Spagat Cadence",
+ archetype_key="initiative.maturity_journey",
+ )
+ initiative_id = created.json()["id"]
+
+ recurring = client.get(
+ f"/api/initiatives/{initiative_id}/recurring",
+ headers=_auth(token),
+ ).json()
+ assert recurring
+ item = recurring[0]
+ assert item.get("open_cadence_instance")
+ assert item["open_cadence_instance"]["status"] == "open"
+ recurring_id = item["id"]
+
+ completed = client.post(
+ f"/api/recurring/{recurring_id}/complete",
+ json={"measurement_note": "45° Spagat gehalten"},
+ headers=_auth(token),
+ )
+ assert completed.status_code == 200
+ body = completed.json()
+ assert body["completed_instance"]["status"] == "completed"
+ assert body["completed_instance"]["measurement_note"] == "45° Spagat gehalten"
+ assert body["next_instance"]["status"] == "open"
+ assert body["recurring"]["next_due_at"]
+
+ refreshed = client.get(
+ f"/api/initiatives/{initiative_id}/recurring",
+ headers=_auth(token),
+ ).json()
+ assert refreshed[0]["open_cadence_instance"]["id"] == body["next_instance"]["id"]
+
+
+def test_steering_next_action_includes_recurring_id(client):
+ user = provision_user_in_tenant(tenant_role="admin")
+ token = _login(client, user)
+
+ created = _create_initiative(
+ client,
+ token,
+ title="Spagat Next",
+ archetype_key="initiative.maturity_journey",
+ )
+ initiative_id = created.json()["id"]
+
+ snap = client.get(
+ f"/api/initiatives/{initiative_id}/steering-snapshot",
+ headers=_auth(token),
+ )
+ assert snap.status_code == 200
+ next_actions = snap.json().get("next_actions") or []
+ recurring_items = [a for a in next_actions if a.get("kind") == "recurring_due"]
+ assert recurring_items
+ assert recurring_items[0].get("recurring_id")
diff --git a/backend/tests/test_ap23a_operating_context.py b/backend/tests/test_ap23a_operating_context.py
index fa873a4..a881c8e 100644
--- a/backend/tests/test_ap23a_operating_context.py
+++ b/backend/tests/test_ap23a_operating_context.py
@@ -82,6 +82,8 @@ def test_operating_context_maturity(client):
body = res.json()
assert body["method_key"] == "maturity_progression"
assert any(s["key"] == "journey" for s in body["ui_profile"]["processSteps"])
+ assert "inbox" not in (body["ui_profile"].get("planOutlineKeys") or [])
+ assert body["ui_profile"]["planOutlineKeys"] == ["profile", "gates", "work"]
def test_operating_context_program(client):
diff --git a/docs/product/Kairo_Implementation_Truth_Table_v0.1.md b/docs/product/Kairo_Implementation_Truth_Table_v0.1.md
index 5d852e2..ecd75ad 100644
--- a/docs/product/Kairo_Implementation_Truth_Table_v0.1.md
+++ b/docs/product/Kairo_Implementation_Truth_Table_v0.1.md
@@ -176,7 +176,7 @@ Verhindert, dass Zielbild-Dokumente als Ist-Stand gelesen werden.
| maturity / sequential / queue / recurring Strategien | ✓ | AP2.0d; pytest + Deploy |
| Referenz-Ausprägungen (Kumite, Buch, Kairo) | ◐ | Code-Registry; Nutzer wählt bei Anlage |
| Archetyp Starter-Kits | ◐ | AP2.2a |
-| MVP-Abnahfe Stufe A validiert | ◐ | AP2.1: Report v0.3 + Assignment — PO-Validation auf Dev ausstehend |
+| MVP-Abnahfe Stufe A validiert | ✗ | AP2.1: A1 PO No-Go; A2/B2b offen — Register §7 |
| Operational Actor API | ✓ | AP1.7: `/api/operational/` + Service Tokens |
| Actor Service Token | ✓ | AP1.7c; Tenant-scoped, capability-gebunden |
| MCP produktiv | ✗ | nach AP2.1 Go |
@@ -220,6 +220,15 @@ Details: `Kairo_Status_Review_and_Next_Steps_v0.1.md` §2.1
| Roadmap-Phasen veraltet | Roadmap v0.2 §2 aktualisiert 2026-07-11 |
| Usability Recovery → InitiativeDetail | IA-first, Redirects AP1.9a |
+| **F-A1-09** | Leitfrage (≤2 Min) | Prozess/Doku | — | offen | Report §3.3 |
+
+---
+
+## 7. Validation-Findings (AP2.1)
+
+PO-Smoke-Ergebnisse und Architektur-Einordnung:
+`docs/sprints/Sprint1_AP2_1_Validation_Findings_Register_v0.1.md`
+
---
## Nächste erwartete Truth-Table-Änderungen
diff --git a/docs/product/Kairo_MVP_Execution_Plan_v0.2.md b/docs/product/Kairo_MVP_Execution_Plan_v0.2.md
index 21dc2e9..2b31363 100644
--- a/docs/product/Kairo_MVP_Execution_Plan_v0.2.md
+++ b/docs/product/Kairo_MVP_Execution_Plan_v0.2.md
@@ -149,6 +149,8 @@ Auth: Actor Service Token (Tenant-scoped). Kein Gate-Verify, kein Lifecycle-Over
Go-Kriterium: MVP v0.3 §5 — pro Stufe-A-Archetyp Leitfrage ≤2 Min + Next Action begründet; Portfolio Cockpit-Attention (Kriterium 7).
+**Findings-Register (PO-Smoke + Architektur-Review):** `docs/sprints/Sprint1_AP2_1_Validation_Findings_Register_v0.1.md`
+
---
## 8. Dokumentenpriorität
diff --git a/docs/product/archetypes/SPEC_A1_maturity_journey_v0.1.md b/docs/product/archetypes/SPEC_A1_maturity_journey_v0.1.md
index 51cd13e..9710dd7 100644
--- a/docs/product/archetypes/SPEC_A1_maturity_journey_v0.1.md
+++ b/docs/product/archetypes/SPEC_A1_maturity_journey_v0.1.md
@@ -228,6 +228,11 @@ Cadence im Detail am RecurringElement.
## 11. Ist-Stand (Spec vs. Code)
+**PO-Validation 2026-07-28:** Detaillierte Findings + Architektur-Urteil →
+`docs/sprints/Sprint1_AP2_1_Validation_Findings_Register_v0.1.md` (F-A1-01…10)
+
+**PO-Produkturteil A1:** **No-Go** — Leitfrage nicht erfüllt; Tages-Tracking, Messgrößen, Gate-Fortschritt und IA fehlen für echten Einsatz. Architektur/Spec **nicht** falsch; **Minimal-Slice** (CadenceInstance, Work=Übung) vor Re-Abnahme nötig.
+
| Bereich | Spec | Code/Truth | Lücke |
|---------|------|------------|-------|
| Registry / Kit | A1 + Stufen | weitgehend AP2.0e/Kit | Multi-Übung/Alternanz |
diff --git a/docs/sprints/Sprint1_AP2_1_MVP_Validation_Report_v0.3.md b/docs/sprints/Sprint1_AP2_1_MVP_Validation_Report_v0.3.md
index 36753cc..67f32f8 100644
--- a/docs/sprints/Sprint1_AP2_1_MVP_Validation_Report_v0.3.md
+++ b/docs/sprints/Sprint1_AP2_1_MVP_Validation_Report_v0.3.md
@@ -4,28 +4,33 @@
**Status:** Entwurf — vom Product Owner auszufüllen
**Stand:** 2026-07-28
**Bezug:** `Kairo_MVP_Definition_v0.3.md` §5–8
-**Assignment:** `Sprint1_AP2_1_MVP_Validation_Assignment_v0.1.md`
+**Assignment:** `Sprint1_AP2_1_MVP_Validation_Assignment_v0.1.md`
+**Findings (Architektur-Review):** `Sprint1_AP2_1_Validation_Findings_Register_v0.1.md`
| Feld | Wert |
|------|------|
-| **Kairo-Version / Deploy** | |
+| **Kairo-Version / Deploy** | `0.20.2-ap2.2d` (schema 025) |
| **Dev-URL** | https://dev.kairo.jinkendo.de |
-| **Branch / Commit** | `develop` @ |
-| **Tester (PO)** | |
-| **Testdatum** | |
+| **Branch / Commit** | `develop` @ `0baa595` |
+| **Tester (PO)** | Lars |
+| **Testdatum** | 2026-07-28 |
---
## 1. Executive Summary
-| Entscheidung | ☐ Go Stufe A · ☐ Bedingt · ☐ No-Go |
+| Entscheidung | ☐ Go Stufe A · ☐ Bedingt · ☑ No-Go (bis A1 nachgezogen oder Scope-PO) |
|--------------|-------------------------------------|
**Kurzfazit (3–5 Sätze):**
+Technisch: pytest grün, A1-Happy-Path (Anlage → Verify → Stufenwechsel) durchspielbar.
+Produkt (PO): **A1 No-Go** — Leitfrage nicht beantwortbar; tagesbezogenes Tracking, Messgrößen und Gate-Fortschritt fehlen; extreme Unübersichtlichkeit; **Hürden > Nutzen**. A2/B2b noch offen.
**Blocker (falls No-Go / Bedingt):**
+- **A1:** CadenceInstance/Tages-„erledigt“, Work=Übung, Gate-Fortschritt, IA — siehe Findings Register §6.1 (F-A1-02…10)
+- **Gesamt Stufe A:** MVP §4 verlangt A1 — Go erst nach A1 Minimum Slice **oder** PO-Scope-Entscheidung (A1 aus Stufe-A-Abnahfe)
---
@@ -44,8 +49,22 @@
## 3. Szenario A1 — Spagat können (Reifegrad)
-**Initiative-ID / Titel:**
-**Archetyp:** `initiative.maturity_journey` · **Methode:** `maturity_progression`
+**Initiative-ID / Titel:** Spagat können (PO-Smoke 2026-07-28)
+**Archetyp:** `initiative.maturity_journey` · **Methode:** `maturity_progression`
+**Zwischenurteil:** ☐ Go · ☐ Bedingt · ☑ **No-Go (Produkt)** — [Findings Register](Sprint1_AP2_1_Validation_Findings_Register_v0.1.md) F-A1-01…10
+
+### 3.0 PO-Feedback (2026-07-28)
+
+| Thema | Urteil |
+|-------|--------|
+| Anlage + Starter-Kit | ✓ technisch |
+| Kontrolle (Stufe, Next Action) | ✗ kein Tages-/Gate-Fortschrittsbild |
+| Ausführen (Anti-Todo-Wand) | ✗ Übung nicht ausführbar; AP-Verwirrung |
+| Verify + Stufenwechsel | ✓ technisch; PO: kein Ersatz für laufendes Tracking |
+| Journey / Rhythmen | ✗ unübersichtlich |
+| Leitfrage ≤2 Min | **✗** — nicht sinnvoll beantwortbar |
+
+**PO-Zitat (Kern):** *Für tagesbezogenes Tracking, Messmethoden und aktive Gate-Einschätzung unbrauchbar; Hürden größer als Nutzen.*
### 3.1 Smoke-Schritte (Dev)
@@ -74,9 +93,9 @@
### 3.3 Leitfrage-Antwort (PO, ≤2 Min)
-**Nächster Schritt:**
-**Warum:**
-**Stopuhr (Min):**
+**Nächster Schritt:** *Nicht sinnvoll bestimmbar* — kein klarer Tages-Handlungspfad (Übung erledigen + Fortschritt sehen).
+**Warum:** Ausführen zeigt AP statt Übung; kein Tracking; Gate-Fortschritt nicht steuerbar; UI unübersichtlich.
+**Stopuhr:** >2 Min / abgebrochen — **MVP §5.6 nicht erfüllt**
### 3.4 Anti-Patterns (§7)
@@ -85,8 +104,7 @@
| Flache Liste aller Übungs-Tasks als Hauptbild | ☐ ja ☐ nein | |
| Recurring im Action-Backlog statt Rhythmus-Ansicht | ☐ ja ☐ nein | |
-**Lücken / UX-Schmerz:**
-
+**Lücken / UX-Schmerz:** PO No-Go — Register F-A1-02…10; Minimum Slice §6.1 vor Re-Test. Kein Architektur-Neudesign — fehlender A1-Kern (CadenceInstance, Today=Übung, Gate-Fortschritt, IA).
---
diff --git a/docs/sprints/Sprint1_AP2_1_Validation_Findings_Register_v0.1.md b/docs/sprints/Sprint1_AP2_1_Validation_Findings_Register_v0.1.md
new file mode 100644
index 0000000..746a751
--- /dev/null
+++ b/docs/sprints/Sprint1_AP2_1_Validation_Findings_Register_v0.1.md
@@ -0,0 +1,229 @@
+# AP2.1 — Validation Findings Register
+## v0.1 (PO-Smoke → Architektur-Review)
+
+**Status:** aktiv — wird während AP2.1 ergänzt
+**Stand:** 2026-07-28 (A1 Minimum Slice implementiert — PO Re-Abnahme ausstehend)
+**Bezug:** `Sprint1_AP2_1_MVP_Validation_Report_v0.3.md`, `Kairo_MVP_Definition_v0.3.md` §5–8
+**Dev:** https://dev.kairo.jinkendo.de · nach Deploy Migration **030** + A1-Slice
+
+---
+
+## 1. Zweck
+
+Dieses Register hält **PO-Beobachtungen aus der MVP-Validation** fest und ordnet sie architektonisch ein:
+
+| Kategorie | Bedeutung |
+|-----------|-----------|
+| **Bug** | Abweichung vom **aktuellen** Soll (Spec + gelieferte Architektur) — Fix ohne ADP |
+| **Config-Drift** | Inkonsistenz innerhalb der Plugin-/Profile-Schicht (kein Modellbruch) |
+| **Implementierungsschuld** | Architektur und Spec stimmen; Code liefert Teilmenge |
+| **Aufgeschoben (Spec)** | Explizit in Archetyp-/Methoden-Spec oder ADP als „später“ / Stufe B+ |
+| **Architektur-Lücke** | Zielmodell dokumentiert, tragfähige Basis fehlt oder widerspricht sich **über** Spec hinaus |
+
+**Leitfrage des Reviews:** Brauchen wir ein **neues Architektur-Design**, oder ist die Plugin-/Kernel-Schicht korrekt und es fehlen **Slices**?
+
+---
+
+## 2. Architektur-Urteil (Stand nach A1-Smoke)
+
+### 2.1 Kurzfazit (technisch)
+
+**Kein generelles Architektur-Design-Problem.** Die vier Schichten (Methode → Archetyp → Profil → UI-Composition, AP2.3/AP2.4) tragen den A1-Happy-Path **technisch**.
+
+### 2.1b PO-Produkturteil A1 (2026-07-28)
+
+**No-Go für echten Einsatz** — unabhängig vom grünen E2E-Test.
+
+> Leitfrage lässt sich nicht sinnvoll beantworten. Für tagesbezogenes Tracking (durchgeführt; optional Messwerte wie Anzahl, 45°-Spagat), aktive Einschätzung des Gate-Fortschritts und übersichtliche Steuerung ist der Vorhabentyp **unbrauchbar**. Hürden > Nutzen.
+
+| Dimension | Technische Validation | PO-Produktnutzen |
+|-----------|----------------------|------------------|
+| Happy Path (Anlage → Verify) | ✓ | ◐ — nur Demo, kein Alltag |
+| Leitfrage ≤2 Min (MVP §5.6) | — | **✗** |
+| Tages-Tracking / Übung erledigen | ✗ | **Blocker** |
+| Messgrößen / Metriken | ✗ | **Blocker** (PO) |
+| Gate-Fortschritt sichtbar steuerbar | ◐ | **✗** |
+| Übersichtlichkeit | ◐ | **✗** |
+
+**Architektur-Einordnung:** PO-Anforderungen stehen **bereits in SPEC A1 / Spec-D** (CadenceInstance, Tages-Übung, Kriterien, Metriken). Es fehlt nicht ein neues Design — es fehlt der **Minimal-Slice für nutzbaren A1**, der fälschlich als „post-MVP“ behandelt wurde.
+
+**Konsequenz AP2.1 / MVP Stufe A:** A1 erfüllt **nicht** die MVP-Nutzbarkeits-Bar §5 für PO-Abnahme. Gesamt-Go Stufe A **blockiert**, solange A1 Pflicht-Szenario bleibt (`Kairo_MVP_Definition_v0.3.md` §4).
+
+Die Validation zeigt drei **Lückentypen**:
+
+```text
+(1) Config-Drift planOutlineKeys ≠ dataSlices → schneller Fix
+(2) Spec-Deferred CadenceInstance, Alternanz, metric → geplant, Spec §11
+(3) Polymorphie-Gap Kernel Q2 Work-Items: BE ja, FE nein → Implementierungsschuld
+```
+
+### 2.2 Referenz-Architektur (Prüfmaßstab)
+
+| Dokument | Rolle |
+|----------|--------|
+| `ADP_Archetype_Method_Plugin_Architecture_v0.1.md` | Route Gating, Slices, kein Page-If |
+| `SPEC_A1_maturity_journey_v0.1.md` §4, §8, §11 | A1-Fachmodell + Ist-Stand-Tabelle |
+| `methods/SPEC_D_maturity_progression_v0.1.md` D4–D6 | Leading = CadenceInstance; Ranking |
+| `ADP_Roadmap_Graph_and_Gate_Checklist_v0.1.md` | Kriterien, Evidence, `metric` später |
+| `Kairo_Steering_Method_Kernel_v0.1.md` Q2 | Work-Item-Polymorphie |
+| `Kairo_Steering_Method_Normalization_Program_v0.1.md` | CadenceInstance als Normalform |
+
+### 2.3 Was **nicht** neu designt werden muss
+
+- Archetyp ↔ Methode ↔ UI-Profil (Operating Context)
+- Gate-Verify + Kriterien-Checkliste (AP1.4b)
+- Stufenwechsel + Recurring-Rotation (AP2.0e)
+- Composition-Slots (`maturity_stage`, `recurring_rhythm`, `work.today`)
+
+---
+
+## 3. Findings — Szenario A1 (Spagat / Reifegrad)
+
+**PO-Validation:** 2026-07-28 · Vorhaben „Spagat können“ · `initiative.maturity_journey`
+**Urteil A1:** **No-Go (Produkt)** · technischer Kernpfad ✓ · MVP §5 für PO **✗**
+
+| ID | PO-Beobachtung | Kategorie | Architektur-Urteil | MVP Stufe A | Follow-up |
+|----|----------------|-----------|-------------------|-------------|-----------|
+| **F-A1-01** | Klick **Plan → Eingang** leitet auf **Zielzustände** um | **Config-Drift** | `planOutlineKeys` enthält `inbox`, `dataSlices` ohne `backlog` → Route Gating korrekt, Nav falsch. **Kein Modellproblem.** Spec A1 §8: Eingang „nicht dominant“. | Mitverantwortlich Unübersicht | **Fix:** `inbox` aus `planOutlineKeys` bei `initiative.maturity_journey` |
+| **F-A1-02** | Keine Konfiguration **Wochentage / Rhythmus**; kein **Tages-Tracking** („heute erledigt“) | **Implementierungsschuld** (Spec-Pflicht) | Spec A1 §4.3–4.5 + Spec-D D4: `CadenceInstance`. Code: nur `RecurringElement` + Fälligkeit. **Kein Designbruch — fehlender Kern-Slice.** | **PO-Blocker** | **AP A1-Cadence-MVP:** Instanz + „heute erledigt“ |
+| **F-A1-03** | **Ausführen:** AP statt Übung; Rhythmus-Link tot; AP nicht öffenbar | **Implementierungsschuld** | Kernel Q2: Leading = CadenceInstance; FE verlinkt nur Actions. | **PO-Blocker** | **AP A1-Work-UI** |
+| **F-A1-04** | Kriterien auto-erfüllt via Evidence; keine laufende Gate-Einschätzung | **By Design** + **Lücke UX** | AP1.4b korrekt für Verify-Moment; PO braucht **Fortschrittsbild** während Stufe (Spec A1 §8 Kontrolle). | **PO-Blocker** (Einschätzung) | Fortschritts-Panel + manuelle/metric Kriterien |
+| **F-A1-05** | Keine Messung (4×/Woche, 80 Punkte, 45° Spagat, …) | **Implementierungsschuld** (Spec vorbereitet) | `criterion_kind: metric` + Cadence-Erfüllung geplant; keine Engine. Schema reicht — **Feature fehlt, kein ADP neu.** | **PO-Blocker** | Messwert am Tagesabschluss + optional metric-Kriterium |
+| **F-A1-06** | Keine **kontinuierliche** Fortschrittskontrolle in der Stufe | **Implementierungsschuld** | Plan/Ist-Trennung korrekt; Ist-Tracking für Übungen fehlt (CadenceInstance). | **PO-Blocker** | Teil A1-Cadence-MVP |
+| **F-A1-07** | Nach Verify keine AP-Aktivierung; nur Recurring rotiert | **Implementierungsschuld** | Spec A1 §4.4 Activity Set — Scope AP2.0e minimal. | Sekundär vs. F-A1-02–06 | AP A1-Activity-Set |
+| **F-A1-08** | Journey unklar; pausierte Übung verwirrend | **IA** | Technisch korrekt (AP2.0e); PO wahrgenommene Unübersicht. | Mitverantwortlich | IA A1 Today-first |
+| **F-A1-09** | **Leitfrage** nicht beantwortbar — Hürden > Nutzen | **PO-Produkturteil** | MVP §5.6 nicht erfüllt. Symptom von F-A1-02…05, nicht Doku-Problem. | **No-Go** | siehe §8 Minimum-Slice |
+| **F-A1-10** | **Extreme Unübersichtlichkeit** (Eingang-Redirect, AP vs. Übung, Journey vs. Rhythmen) | **IA + Implementierungsschuld** | Plugin-Architektur OK; **A1-Profil und Work-Default** spiegeln Nutzerbild Spec §8 nicht. Kein neues OM nötig — **falsche/fehlende UI-Hülle**. | **PO-Blocker** | A1-IA-Slice (Eingang weg, Today=Übung, klare Stufenleiste) |
+
+---
+
+## 4. Detail-Notizen (Architektur-Check)
+
+### F-A1-01 — Eingang vs. Slices
+
+```text
+ui_profiles.py initiative.maturity_journey:
+ planOutlineKeys: [profile, gates, inbox, work] ← Nav zeigt Eingang
+ dataSlices: [actions, roadmap, recurring, …] ← kein "backlog"
+
+viewRegistry.js /plan/inbox requiredSlices: ['backlog']
+→ isRouteAllowedForMode = false → Redirect planDefaultRoute (/plan/gates)
+```
+
+**Urteil:** Plugin-Architektur funktioniert wie designed; **Profil-Seed ist inkonsistent**.
+
+### F-A1-02 / F-A1-06 — CadenceInstance-Schicht
+
+| Schicht | Spec | Code heute |
+|---------|------|------------|
+| RecurringElement | Container + Bindung Stufe | ✓ |
+| Cadence / Wochentage / Alternanz | Spec A1 §4.3–4.5 | ✗ |
+| CadenceInstance (eine offene Instanz) | Spec-D D4, Kernel Q2 | ✗ (nur `next_due_at` am Element) |
+| Erfüllung → nächste Instanz | Spec A1 Happy Path §9.3 | ◐ (manuell / overdue-Logik minimal) |
+
+**Urteil:** **Kein falsches Design** — Normalization-Programm beschreibt Ziel; MVP lieferte **Minimal-Rhythmus**. Spec A1 §11 listet das explizit als Lücke.
+
+### F-A1-03 — Work-Item-Polymorphie (Kernel Q2)
+
+| Work Item | Next Action BE | Next Action FE Link |
+|-----------|----------------|---------------------|
+| Action | ✓ | ✓ |
+| Backlog | ✓ | ✓ |
+| Recurring / Cadence | ✓ (`recurring_due`) | ✗ (nur Text `recommended_action`) |
+
+**Urteil:** **Architektur-Lücke nur in der Präsentationsschicht** — Steering-Kernel und Strategies sind erweiterbar; FE muss polymorphe Ziele unterstützen (Link-Typ-Registry analog Backend `kind`).
+
+### F-A1-04 / F-A1-05 — Kriterien & Metriken
+
+- **Evidence → satisfied:** beabsichtigt (`roadmap_criteria.prepare_criteria_for_verify`).
+- **Manuelle Kriterien:** bleiben offen bis manuell / waived — korrekt.
+- **Metriken:** Schema vorbereitet, **keine** Regel-Engine — ADP „später“.
+
+**Urteil:** Gate-Modell ist **generisch genug**; fehlende Metrik-Auswertung ist **Feature-Deferral**, kein Redesign.
+
+### F-A1-07 — Activity Set bei Stufenwechsel
+
+`maturity_stage_transition.on_maturity_stage_reached`:
+
+- pausiert aktive Recurring ✓
+- aktiviert nächste Stufe ✓
+- legt neue Recurring-Übung an ✓
+- **keine** Stage-Actions, **keine** Graph-Aktivierung weiterer Plan-APs
+
+Spec A1 §4.4 verlangt volles Set — **Implementierungsschuld**, Scope AP2.0e war Minimal-Slice.
+
+---
+
+## 5. Abgleich mit SPEC_A1 §11 (Ist-Stand-Tabelle)
+
+| Spec §11 Zeile | Validation bestätigt |
+|----------------|-------------------|
+| Multi-Übung / Alternanz fehlt | F-A1-02 ✓ |
+| Stage Activity Set Teilmenge | F-A1-07 ✓ |
+| UI Stufe-Detail lückenhaft | F-A1-02, F-A1-03 ✓ |
+| steering_elements FE/BE Parität | F-A1-03 (recurring in Kontrolle ✓, Work ◐) |
+
+**Keine neue Spec-Zeile nötig** — Register präzisiert PO-Sicht für AP2.1.
+
+**Urteil:** **Kein falsches Design** — PO-Anforderung = Spec §4 / Spec-D. MVP lieferte **Infrastruktur ohne Ist-Schicht**. Für PO-Abnahme ist das **kein „später“**, sondern **fehlender A1-Kern**.
+
+### F-A1-09 / F-A1-10 — PO-Leitfrage & Unübersichtlichkeit
+
+**PO (2026-07-28, wörtlich zusammengefasst):**
+
+- Leitfrage nicht sinnvoll beantwortbar
+- Tages-Tracking + optional Messwerte (Anzahl, Grad, …) + aktive Gate-Einschätzung **erforderlich** für echten Einsatz
+- Extreme Unübersichtlichkeit → **Hürden größer als Nutzen**
+
+**Architektur:** Symptome von fehlendem CadenceInstance-Modell, Work-UI auf AP statt Übung, IA-Rauschen (Eingang, Journey). **Kein Widerspruch** zwischen Vision und Spec — **Widerspruch zwischen geliefertem MVP-Slice und PO-Mindestnutzen**.
+
+---
+
+## 6. Priorisierte Follow-ups (PO-gesteuert)
+
+### 6.1 A1 Minimum Usability Slice (vor erneuter A1-Abnahme)
+
+**Implementiert (2026-07-28, uncommitted):** Migration 030, `cadence_instance` + `POST /api/recurring/{id}/complete`, `WorkTodayPracticePanel`, Next-Action-Link `#today-practice`, Gate-Fortschritt in `MaturityStagePanel`, Eingang aus A1-Nav. **Offen:** PO Re-Abnahme; Metric-Kriterium manuell (Slice #5, optional vor Re-Test).
+
+| # | Lieferung | Decke PO-Schmerz | Status |
+|---|-----------|------------------|--------|
+| 1 | **CadenceInstance + „Heute erledigt“** | Tages-Tracking | ✓ implementiert |
+| 2 | **Ausführen/Today = Übung** | Nutzen Alltag | ✓ implementiert |
+| 3 | **Kontrolle: Gate-Fortschritt** | Gate-Einschätzung | ✓ implementiert (Kriterien offen/erfüllt) |
+| 4 | **IA A1** (Eingang weg) | Unübersichtlichkeit | ✓ implementiert |
+| 5 | **Metric-Kriterium manuell** | Messgrößen | ◐ Messnotiz am Complete; Auto-Engine weiter offen |
+
+Alternanz, volle Metric-Engine, Activity-Set-Automation → **nach** Slice 1–4.
+
+### 6.2 Technische Quick-Wins (P0)
+
+| Prio | ID | Slice |
+|------|-----|-------|
+| P0 | F-A1-01 | Eingang aus A1-Nav |
+| P1 | F-A1-03 | Next Action → Recurring/Übung-Aktion |
+
+---
+
+## 7. Konsequenz für AP2.1 / MVP Stufe A
+
+| Option | Bedeutung |
+|--------|-----------|
+| **A — A1 nachziehen** | Validation pausieren; **A1 Minimum Slice** (§6.1) liefern; A1 erneut smoke-testen |
+| **B — MVP-Definition anpassen** | A1 vorübergehend aus Stufe-A-Abnahfe (PO-Entscheidung + Doku) — **nur wenn A2+B2b als MVP reichen** |
+| **C — Gesamt No-Go** | AP2.1 stoppt bei Stufe A bis A1 oder Scope-Entscheidung |
+
+**Empfehlung:** **Option A** — Architektur trägt; Spec war richtig; Implementierung war zu dünn. A2/B2b-Smoke **parallel** möglich, Gesamt-Go aber nicht ohne A1-Lösung (Option A oder explizite B).
+
+---
+
+## 8. Nächste Register-Einträge
+
+| Szenario | Status |
+|----------|--------|
+| A2 — Neue Küche (Linear) | ausstehend |
+| B2b + B3 — Product + Sprint | ausstehend |
+| Portfolio / Cockpit (Kriterium 7) | ausstehend |
+
+---
+
+*Aktualisieren bei jedem PO-Smoke-Schritt. Bei Kategorie „Architektur-Lücke“ → ADP prüfen, nicht sofort implementieren.*
diff --git a/docs/sprints/Sprint1_AP_A1_Minimum_Usability_Slice_v0.1.md b/docs/sprints/Sprint1_AP_A1_Minimum_Usability_Slice_v0.1.md
new file mode 100644
index 0000000..f761430
--- /dev/null
+++ b/docs/sprints/Sprint1_AP_A1_Minimum_Usability_Slice_v0.1.md
@@ -0,0 +1,61 @@
+# AP A1 — Minimum Usability Slice
+## Auftrag v0.1
+
+**Status:** freigegeben (PO folgt Option A, 2026-07-28) · **Implementierung A1.1–A1.3 abgeschlossen** (Re-Abnahme ausstehend)
+**Auslöser:** AP2.1 PO No-Go A1 — [`Sprint1_AP2_1_Validation_Findings_Register_v0.1.md`](Sprint1_AP2_1_Validation_Findings_Register_v0.1.md) §6.1
+**Ziel:** A1 **real nutzbar** für Tages-Tracking + Gate-Einschätzung + klare Today-Ansicht — **ohne** Architektur-Neudesign.
+
+---
+
+## 1. Nicht-Ziele (dieser Slice)
+
+- Alternanz-Gruppen, Wochentags-Editor vollständig
+- Automatische Metric-Engine (4×/Woche auto-count)
+- Volles Activity Set bei Stufenwechsel (Stage-APs auto-aktivieren)
+- A3/Care CadenceInstance-Parität
+
+---
+
+## 2. Lieferumfang (Pflicht für Re-Abnahme A1)
+
+| # | Lieferung | Findings | Akzeptanz |
+|---|-----------|----------|-----------|
+| 1 | **`cadence_instances`** + API `complete` | F-A1-02, F-A1-06 | Eine offene Instanz pro aktivem Recurring; „Heute erledigt“ → nächste Fälligkeit |
+| 2 | **`WorkTodayPracticePanel`** | F-A1-03 | Ausführen/Today zeigt Übung zuerst; optional Messnotiz |
+| 3 | **Next Action → Übung** | F-A1-03 | Klickbarer Pfad zu Today-Übung |
+| 4 | **Gate-Fortschritt** am aktiven Stufe-Panel | F-A1-04 | Kriterien offen/erfüllt sichtbar + Link Gate-Detail |
+| 5 | **IA A1** | F-A1-01, F-A1-10 | Eingang aus Nav; Plan-Outline nur profile + gates + work |
+
+---
+
+## 3. Architektur-Bezug (kein ADP nötig)
+
+| Baustein | Dokument |
+|----------|----------|
+| CadenceInstance | `Kairo_Steering_Method_Normalization_Program_v0.1.md`, Spec-D `maturity_progression` D4 |
+| Work-Item-Polymorphie FE | Kernel Q2 — Link-Typ `recurring_id` |
+| Gate-Kriterien | AP1.4b — bestehend |
+
+---
+
+## 4. Pakete
+
+```text
+A1.1 Migration 030 + cadence_instance service + complete API + pytest
+A1.2 WorkTodayPracticePanel + Composition + NextAction-Link
+A1.3 MaturityStagePanel Gate-Fortschritt + ui_profile IA
+A1.4 Re-Validation A1 (PO) → Report §3 Go/Bedingt
+```
+
+---
+
+## 5. Re-Abnahme-Kriterien (PO)
+
+1. PO kann **heute** Übung als erledigt markieren (+ optional Messnotiz).
+2. **Leitfrage ≤2 Min:** „Welche Übung heute?“ — ohne AP-Detour.
+3. **Kontrolle:** aktive Stufe + Kriterien-Fortschritt erkennbar.
+4. Kein Eingang-Redirect / Nav-Rauschen.
+
+---
+
+*Nach Abschluss: Findings Register F-A1-* auf erledigt setzen; AP2.1 A1 erneut smoke-testen.*
diff --git a/frontend/src/api/recurring.js b/frontend/src/api/recurring.js
index a814109..2112d35 100644
--- a/frontend/src/api/recurring.js
+++ b/frontend/src/api/recurring.js
@@ -21,3 +21,10 @@ export function updateRecurring(recurringId, body) {
export function deleteRecurring(recurringId) {
return apiFetch(`/api/recurring/${recurringId}`, { method: 'DELETE' })
}
+
+export function completeRecurringPractice(recurringId, body = {}) {
+ return apiFetch(`/api/recurring/${recurringId}/complete`, {
+ method: 'POST',
+ body: JSON.stringify(body),
+ })
+}
diff --git a/frontend/src/components/MaturityStagePanel.jsx b/frontend/src/components/MaturityStagePanel.jsx
index 97a1a34..3bc6fe9 100644
--- a/frontend/src/components/MaturityStagePanel.jsx
+++ b/frontend/src/components/MaturityStagePanel.jsx
@@ -1,28 +1,66 @@
+import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { MILESTONE_STATUS_LABELS } from '../constants/status.js'
+import { listInitiativeRoadmapCriteriaProgress } from '../api/roadmap.js'
+import { gatePath, scopedPath } from '../utils/routes.js'
import { EmptyState } from './EmptyState.jsx'
-import { scopedPath } from '../utils/routes.js'
export function MaturityStagePanel({
initiativeId,
roadmapItems = [],
embedded = true,
}) {
+ const [criteriaProgress, setCriteriaProgress] = useState({})
+
const stages = [...roadmapItems]
.filter((item) => item.item_type === 'maturity_stage')
.sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0))
const activeStage = stages.find((s) => s.status === 'active')
+ useEffect(() => {
+ if (!initiativeId) return undefined
+ let cancelled = false
+ listInitiativeRoadmapCriteriaProgress(initiativeId)
+ .then((data) => {
+ if (!cancelled) setCriteriaProgress(data || {})
+ })
+ .catch(() => {
+ if (!cancelled) setCriteriaProgress({})
+ })
+ return () => {
+ cancelled = true
+ }
+ }, [initiativeId, roadmapItems.length])
+
+ const activeProgress = activeStage ? criteriaProgress[activeStage.id] : null
+
const body =
stages.length === 0 ? (
- Aktive Stufe: {activeStage.title} -
+ <> ++ Aktive Stufe:{' '} + + {activeStage.title} + +
+ {activeProgress && ( ++ Gate-Fortschritt:{' '} + + {activeProgress.closed ?? 0} / {activeProgress.total ?? 0} + {' '} + Kriterien erfüllt + {(activeProgress.open ?? 0) > 0 && ( + · {activeProgress.open} offen + )} +
+ )} + > )}- Stufen und Übungen unter{' '} - - Kontrolle → Rhythmen & Journey + Kriterien und Verify am{' '} + + Plan → Stufen + + ; Übung unter{' '} + + Ausführen → Heute .
@@ -62,7 +106,7 @@ export function MaturityStagePanel({- Aktive Stufe und Fortschritt — Steuerungshorizont für Reifegrad-Vorhaben (A1). + Aktive Stufe, Gate-Fortschritt und Orientierung — Steuerungshorizont (A1).
+ Tages-Tracking — erledigt markieren, optional Messwert notieren (z. B. Grad, Wiederholungen). +
+Fällig: {formatDue(openInstance.due_at)}
+ )} +Keine Berechtigung zum Markieren der Übung.
+ )} +