#!/usr/bin/env python3
"""Phase-13: undo-toast, breadcrumb, in-app-changelog, ARIA, kpi-comparison."""
from __future__ import annotations
import asyncio
import datetime
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from phase2_features import Feature, FileGen, ROOT, log, log_section # noqa: E402
from phase3_features import run_feature_v2 # noqa: E402
PHASE13_STATE = ROOT / ".phase13-state.json"
FEATURES: list[Feature] = [
Feature(
name="undo-toast",
description="Toast mit Undo-Action für deletes",
files=[
FileGen(
path="apps/web/src/components/Toast.tsx",
purpose=(
"ERWEITERT — behalte alle bestehenden Methoden (success/error/info). Füge eine: "
"undoable({message, onUndo}) — zeigt Toast mit message + 'Rückgängig'-Button, "
"wenn Klick: ruft onUndo, schließt Toast. Auto-dismiss nach 8s (länger als normal)."
),
refs=["apps/web/src/components/Toast.tsx"],
),
],
),
Feature(
name="breadcrumb-navigation",
description="Breadcrumb-Komponente, top der Detail-Pages",
files=[
FileGen(
path="apps/web/src/components/Breadcrumb.tsx",
purpose=(
"Breadcrumb-Component. Props: items: [{label, to?}] (last item ohne to = current page). "
"Render: Home > Customers > Customer-Name als Text-Links + chevron. "
"Tailwind text-sm text-gray-500 with hover-darker."
),
),
FileGen(
path="apps/web/src/pages/CustomerDetail.tsx",
purpose=(
"ERWEITERT — behalte alles. Füge "
"ganz oben."
),
refs=["apps/web/src/pages/CustomerDetail.tsx"],
),
FileGen(
path="apps/web/src/pages/ProjectDetail.tsx",
purpose=(
"ERWEITERT — behalte alles. Füge analog Breadcrumb mit Projects-Pfad."
),
refs=["apps/web/src/pages/ProjectDetail.tsx"],
),
],
),
Feature(
name="in-app-changelog",
description="Changelog-Modal mit Versions-History",
files=[
FileGen(
path="apps/web/src/components/ChangelogModal.tsx",
purpose=(
"Changelog-Modal. Inline-Array CHANGELOG = [{version, date, changes: [strings]}] mit "
"5 fake Einträgen (z.B. 0.0.1 Initial release, 0.0.2 added Projects, etc). "
"Trigger via prop {open, onClose}. Tailwind fixed inset-0 + centered card mit Versionsliste."
),
),
FileGen(
path="apps/web/src/components/VersionBadge.tsx",
purpose=(
"ERWEITERT — behalte VersionBadge. Klick öffnet jetzt (useState open). "
"Statt new-tab-Link."
),
refs=["apps/web/src/components/VersionBadge.tsx"],
),
],
),
Feature(
name="aria-improvements",
description="Accessibility-Improvements (ARIA labels) in Nav + Forms",
files=[
FileGen(
path="apps/web/src/components/Nav.tsx",
purpose=(
"ERWEITERT — füge aria-label zu allen icon-only-Buttons (Theme-Toggle, Lang-Toggle, Logout, CommandPalette-trigger). "
"role='navigation' aufs nav-element. Active-Link mit aria-current='page'. "
"Behalte alles, nur a11y polish."
),
refs=["apps/web/src/components/Nav.tsx"],
),
],
),
Feature(
name="kpi-comparison",
description="Dashboard KPI-Karten mit Vergleich zur Vorwoche",
files=[
FileGen(
path="apps/web/src/pages/Dashboard.tsx",
purpose=(
"ERWEITERT — behalte alles (Stats-Cards, Chart, ActivityFeed, Export-Button). "
"Pro Stats-Karte: zusätzliche Mini-Zeile 'vs. Vorwoche: +12%' (grün) oder '-5%' (rot). "
"Berechnung: fetch time-entries letzte 14 Tage, split current/previous week, calc delta."
),
refs=["apps/web/src/pages/Dashboard.tsx"],
),
],
),
]
def load_state() -> dict:
if PHASE13_STATE.exists():
return json.loads(PHASE13_STATE.read_text())
return {"completed_features": [], "current_feature": None, "started_at": datetime.datetime.now().isoformat()}
def save_state(state: dict) -> None:
PHASE13_STATE.write_text(json.dumps(state, indent=2))
async def main() -> int:
log_section("🚀 Phase-13 Codegen-Run gestartet")
state = load_state()
for feature in FEATURES:
if feature.name in state.get("completed_features", []):
continue
state["current_feature"] = feature.name; save_state(state)
try:
success = await run_feature_v2(feature)
if success:
state.setdefault("completed_features", []).append(feature.name)
else:
state.setdefault("attempted_features", []).append(feature.name)
save_state(state)
except Exception as e:
log(f"❌ {feature.name} crashed: {e}", level="ERROR")
state.setdefault("attempted_features", []).append(feature.name); save_state(state)
log_section("Phase-13 Run beendet")
log(f"OK: {len(state.get('completed_features', []))}, Attempted: {len(state.get('attempted_features', []))}, Total: {len(FEATURES)}")
return 0
if __name__ == "__main__":
sys.exit(asyncio.run(main()))