#!/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()))