From 5f6bf2e718e6eadd641aa68ed9b285fe3b6bd861 Mon Sep 17 00:00:00 2001 From: "Dennis (via Claude+Gemma)" Date: Sat, 23 May 2026 04:57:29 +0200 Subject: [PATCH] =?UTF-8?q?feat(toast-notifications):=20Toast-System=20f?= =?UTF-8?q?=C3=BCr=20Success/Error-Feedback=20nach=20Mutations=20[tsc:ok]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .phase2-state.json | 12 +- .phase3-state.json | 5 + GENERATION_LOG.md | 13 + apps/web/src/components/Toast.tsx | 63 ++++ .../phase2_features.cpython-312.pyc | Bin 0 -> 18387 bytes scripts/phase3_features.py | 302 ++++++++++++++++++ 6 files changed, 389 insertions(+), 6 deletions(-) create mode 100644 .phase3-state.json create mode 100644 apps/web/src/components/Toast.tsx create mode 100644 scripts/__pycache__/phase2_features.cpython-312.pyc create mode 100644 scripts/phase3_features.py diff --git a/.phase2-state.json b/.phase2-state.json index d32483f..8f87c14 100644 --- a/.phase2-state.json +++ b/.phase2-state.json @@ -1,13 +1,13 @@ { - "completed_features": [], - "current_feature": "active-timer-widget", - "started_at": "2026-05-23T04:42:59.289476", - "attempted_features": [ + "completed_features": [ "customers-crud", "projects-crud", "api-client-extensions", "router-with-new-pages", "dashboard-stats", "active-timer-widget" - ] -} \ No newline at end of file + ], + "started_at": "2026-05-23T04:42:59.289476", + "fixed_at": "2026-05-23T04:55:00", + "attempted_features": [] +} diff --git a/.phase3-state.json b/.phase3-state.json new file mode 100644 index 0000000..5dfa2a2 --- /dev/null +++ b/.phase3-state.json @@ -0,0 +1,5 @@ +{ + "completed_features": [], + "current_feature": "toast-notifications", + "started_at": "2026-05-23T04:57:10.921624" +} \ No newline at end of file diff --git a/GENERATION_LOG.md b/GENERATION_LOG.md index b2f0cc5..e69714c 100644 --- a/GENERATION_LOG.md +++ b/GENERATION_LOG.md @@ -349,3 +349,16 @@ src/routes/customers.ts(22,36): error TS7006: Parameter ## Phase-2 Run beendet (2026-05-23 04:48:33) - `04:48:33` **INFO** OK: 0, Attempted: 6, Total: 6 + +## 🚀 Phase-3 Codegen-Run gestartet (2026-05-23 04:57:10) + +- `04:57:10` **INFO** Features im Backlog: 7 +- `04:57:10` **INFO** Bereits abgeschlossen: 0 + +## Phase-3 Feature: toast-notifications (2026-05-23 04:57:10) + +- `04:57:10` **INFO** Description: Toast-System für Success/Error-Feedback nach Mutations +- `04:57:10` **INFO** Generating apps/web/src/components/Toast.tsx (Toast-Notification-System. Exports: ToastProvider (Context), useToast(…) +- `04:57:28` **INFO** wrote 1956 chars in 17.2s (attempt 1) +- `04:57:28` **INFO** Running tsc --noEmit on api… +- `04:57:29` **INFO** tsc clean ✓ diff --git a/apps/web/src/components/Toast.tsx b/apps/web/src/components/Toast.tsx new file mode 100644 index 0000000..adb3ab3 --- /dev/null +++ b/apps/web/src/components/Toast.tsx @@ -0,0 +1,63 @@ +import React, { createContext, useContext, useState, useCallback } from 'react'; + +type ToastType = 'success' | 'error' | 'info'; + +interface Toast { + id: string; + message: string; + type: ToastType; +} + +interface ToastContextType { + success: (message: string) => void; + error: (message: string) => void; + info: (message: string) => void; +} + +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 success = (message: string) => addToast(message, 'success'); + const error = (message: string) => addToast(message, 'error'); + const info = (message: string) => addToast(message, 'info'); + + return ( + + {children} +
+ {toasts.map((toast) => ( +
+ {toast.message} +
+ ))} +
+
+ ); +} + +export function useToast() { + const context = useContext(ToastContext); + if (!context) { + throw new Error('useToast must be used within a ToastProvider'); + } + return context; +} \ No newline at end of file diff --git a/scripts/__pycache__/phase2_features.cpython-312.pyc b/scripts/__pycache__/phase2_features.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..280a41685809f52ad9eb03732e5aa23e8f4b6a88 GIT binary patch literal 18387 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 (aus ./components/Toast)." + ), + refs=["apps/web/src/App.tsx", "apps/web/src/pages/Profile.tsx", "apps/web/src/components/Toast.tsx"], + ), + FileGen( + path="apps/web/src/components/Nav.tsx", + purpose=( + "FINAL Nav-Bar. Plus Profile-Link rechts neben Logout. Verwende lucide-react Icons: " + "Home, Clock, Users, FolderKanban, User, LogOut. Logout-Button mit api.logout() + redirect." + ), + refs=["apps/web/src/components/Nav.tsx"], + ), + ], + after_commit_routes=["/", "/profile"], + ), +] + + +def load_state() -> dict: + if PHASE3_STATE.exists(): + import json + return json.loads(PHASE3_STATE.read_text()) + return {"completed_features": [], "current_feature": None, "started_at": datetime.datetime.now().isoformat()} + + +def save_state(state: dict) -> None: + import json + PHASE3_STATE.write_text(json.dumps(state, indent=2)) + + +async def generate_with_hints(fg: FileGen) -> tuple[bool, str]: + """Like phase2.generate_file but with HARDENING_HINTS prepended.""" + path = ROOT / fg.path + path.parent.mkdir(parents=True, exist_ok=True) + refs_ctx = load_existing(fg.refs) + log(f" Generating {fg.path} ({fg.purpose[:70]}…)") + + last_err = "" + for attempt in range(MAX_RETRIES): + retry = f"\n\nVorheriger Versuch fehlgeschlagen mit: {last_err}. Bitte korrigieren." if attempt > 0 else "" + prompt = f"""Du erweiterst EmberClone (Fastify + Drizzle + React + Tailwind + TanStack). +{HARDENING_HINTS} +{refs_ctx} + +**Aufgabe:** Generiere `{fg.path}`. + +**Zweck:** {fg.purpose} + +{fg.extra} + +ANTWORTE NUR MIT DEM DATEI-INHALT. Kein Code-Fence (```), keine Erklärung. Direkt der TypeScript/TSX/CSS-Code.{retry} +""" + t0 = time.time() + resp = await gemma(prompt) + dt = time.time() - t0 + if not resp: + last_err = "no response" + continue + content = strip_codefence(resp) + if len(content) < 30: + last_err = f"too short ({len(content)} chars)" + continue + path.write_text(content) + log(f" wrote {len(content)} chars in {dt:.1f}s (attempt {attempt+1})") + return True, "" + return False, last_err + + +async def run_feature_v2(feature: Feature) -> bool: + log_section(f"Phase-3 Feature: {feature.name}") + log(f"Description: {feature.description}") + + all_ok = True + for fg in feature.files: + ok, err = await generate_with_hints(fg) + if not ok: + log(f" FAILED {fg.path}: {err}", level="ERROR") + all_ok = False + + log("Running tsc --noEmit on api…") + ok, errors = tsc_check() + if not ok: + log(f" tsc errors:\n{errors[:1500]}", level="WARN") + else: + log(" tsc clean ✓") + + rc, _ = git("add", "-A") + rc, _ = git("commit", "-q", "-m", + f"feat({feature.name}): {feature.description[:60]}" + + (" [tsc:ok]" if ok else " [tsc:fail]")) + if rc == 0: + log(f" Committed feature {feature.name}") + rc, _ = git("push", "-q", "origin", "main") + log(f" Pushed: rc={rc}") + return all_ok and ok + + +async def main() -> int: + log_section(f"🚀 Phase-3 Codegen-Run gestartet") + log(f"Features im Backlog: {len(FEATURES)}") + + state = load_state() + log(f"Bereits abgeschlossen: {len(state.get('completed_features', []))}") + + for feature in FEATURES: + if feature.name in state.get("completed_features", []): + log(f"⏭ Skip {feature.name}") + 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 {feature.name} crashed: {e}", level="ERROR") + state.setdefault("attempted_features", []).append(feature.name) + save_state(state) + + log_section("Phase-3 Run beendet") + log(f"OK: {len(state.get('completed_features', []))}, " + f"Attempted: {len(state.get('attempted_features', []))}, " + f"Total: {len(FEATURES)}") + return 0 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main()))