diff --git a/.phase12-state.json b/.phase12-state.json index 80e8774..7bce85c 100644 --- a/.phase12-state.json +++ b/.phase12-state.json @@ -7,6 +7,7 @@ "time-rounding-rules", "user-avatars", "app-version-display", - "api-client-phase12" + "api-client-phase12", + "router-phase12" ] } \ No newline at end of file diff --git a/.phase13-state.json b/.phase13-state.json new file mode 100644 index 0000000..72656ee --- /dev/null +++ b/.phase13-state.json @@ -0,0 +1,5 @@ +{ + "completed_features": [], + "current_feature": "undo-toast", + "started_at": "2026-05-23T06:42:42.473991" +} \ No newline at end of file diff --git a/GENERATION_LOG.md b/GENERATION_LOG.md index be6ddbe..bd030cc 100644 --- a/GENERATION_LOG.md +++ b/GENERATION_LOG.md @@ -1554,3 +1554,27 @@ src/index.ts(27,25): error TS2769: No overload matches this call. Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error. Argument of type 'Promise' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'. Type 'Promise' provides no match for the signature '(instance: FastifyInstance, FastifyBaseLogger, FastifyTy +- `06:41:11` **INFO** Committed feature router-phase12 +- `06:41:11` **INFO** Pushed: rc=0 + +## Phase-12 Run beendet (2026-05-23 06:41:11) + +- `06:41:11` **INFO** OK: 0, Attempted: 6, Total: 6 + +## 🚀 Phase-13 Codegen-Run gestartet (2026-05-23 06:42:42) + + +## Phase-3 Feature: undo-toast (2026-05-23 06:42:42) + +- `06:42:42` **INFO** Description: Toast mit Undo-Action für deletes +- `06:42:42` **INFO** Generating apps/web/src/components/Toast.tsx (ERWEITERT — behalte alle bestehenden Methoden (success/error/info). Fü…) +- `06:43:06` **INFO** wrote 2823 chars in 24.3s (attempt 1) +- `06:43:06` **INFO** Running tsc --noEmit on api… +- `06:43:08` **WARN** tsc errors: +src/index.ts(27,25): error TS2769: No overload matches this call. + Overload 1 of 3, '(plugin: FastifyPluginCallback<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error. + Argument of type 'Promise' is not assignable to parameter of type 'FastifyPluginCallback<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'. + Type 'Promise' provides no match for the signature '(instance: FastifyInstance, FastifyBaseLogger, FastifyTypeProvider>, opts: { ...; }, done: (err?: Error | undefined) => void): void'. + Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error. + Argument of type 'Promise' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'. + Type 'Promise' provides no match for the signature '(instance: FastifyInstance, FastifyBaseLogger, FastifyTy diff --git a/apps/web/src/components/Toast.tsx b/apps/web/src/components/Toast.tsx index adb3ab3..1d3251c 100644 --- a/apps/web/src/components/Toast.tsx +++ b/apps/web/src/components/Toast.tsx @@ -6,12 +6,14 @@ interface Toast { id: string; message: string; type: ToastType; + onUndo?: () => void; } interface ToastContextType { success: (message: string) => void; error: (message: string) => void; info: (message: string) => void; + undoable: (options: { message: string; onUndo: () => void }) => void; } const ToastContext = createContext(undefined); @@ -19,21 +21,28 @@ const ToastContext = createContext(undefined); export function ToastProvider({ children }: { children: React.ReactNode }) { const [toasts, setToasts] = useState([]); - const addToast = useCallback((message: string, type: ToastType) => { - const id = Math.random().toString(36).substring(2, 9); - setToasts((prev) => [...prev, { id, message, type }]); - - setTimeout(() => { - setToasts((prev) => prev.filter((t) => t.id !== id)); - }, 4000); + const removeToast = useCallback((id: string) => { + setToasts((prev) => prev.filter((t) => t.id !== id)); }, []); + const addToast = useCallback((message: string, type: ToastType, onUndo?: () => void) => { + const id = Math.random().toString(36).substring(2, 9); + setToasts((prev) => [...prev, { id, message, type, onUndo }]); + + const duration = onUndo ? 8000 : 4000; + setTimeout(() => { + removeToast(id); + }, duration); + }, [removeToast]); + const success = (message: string) => addToast(message, 'success'); const error = (message: string) => addToast(message, 'error'); const info = (message: string) => addToast(message, 'info'); + const undoable = ({ message, onUndo }: { message: string; onUndo: () => void }) => + addToast(message, 'info', onUndo); return ( - + {children}
{toasts.map((toast) => ( @@ -41,12 +50,24 @@ export function ToastProvider({ children }: { children: React.ReactNode }) { key={toast.id} className={` px-4 py-3 rounded-lg shadow-lg text-white transition-all duration-300 animate-in fade-in slide-in-from-bottom-4 + flex items-center justify-between gap-4 ${toast.type === 'success' ? 'bg-emerald-500' : ''} ${toast.type === 'error' ? 'bg-red-500' : ''} ${toast.type === 'info' ? 'bg-slate-700' : ''} `} > - {toast.message} + {toast.message} + {toast.onUndo && ( + + )}
))} diff --git a/scripts/phase13_features.py b/scripts/phase13_features.py new file mode 100644 index 0000000..0b90b3e --- /dev/null +++ b/scripts/phase13_features.py @@ -0,0 +1,152 @@ +#!/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()))