diff --git a/.phase22-state.json b/.phase22-state.json index 959b127..e213329 100644 --- a/.phase22-state.json +++ b/.phase22-state.json @@ -6,6 +6,7 @@ "voice-input-stub", "popout-tracker", "project-favicons", - "ui-polish" + "ui-polish", + "screen-recording-attach-stub" ] } \ No newline at end of file diff --git a/.phase23-state.json b/.phase23-state.json new file mode 100644 index 0000000..60db49c --- /dev/null +++ b/.phase23-state.json @@ -0,0 +1,5 @@ +{ + "completed_features": [], + "current_feature": "workspace-logo", + "started_at": "2026-05-23T08:25:49.746920" +} \ No newline at end of file diff --git a/GENERATION_LOG.md b/GENERATION_LOG.md index 133e35e..0c3bf88 100644 --- a/GENERATION_LOG.md +++ b/GENERATION_LOG.md @@ -2680,3 +2680,29 @@ 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 +- `08:23:47` **INFO** Committed feature screen-recording-attach-stub +- `08:23:47` **INFO** Pushed: rc=0 + +## Phase-22 Run beendet (2026-05-23 08:23:47) + +- `08:23:47` **INFO** OK: 0, Attempted: 5, Total: 5 + +## 🚀 Phase-23 Codegen-Run gestartet (2026-05-23 08:25:49) + + +## Phase-3 Feature: workspace-logo (2026-05-23 08:25:49) + +- `08:25:49` **INFO** Description: Logo-Feld auf appSettings + Anzeige in Nav +- `08:25:49` **INFO** Generating apps/api/src/db/schema.ts (WICHTIG: BEHALTE ALLE existierenden Tabellen/Spalten. Füge nur Spalte …) +- `08:26:44` **INFO** wrote 6316 chars in 54.9s (attempt 1) +- `08:26:44` **INFO** Generating apps/web/src/components/Nav.tsx (ERWEITERT — wenn appSettings.logoUrl set, zeige Logo links statt 'Embe…) +- `08:27:35` **INFO** wrote 5539 chars in 50.9s (attempt 1) +- `08:27:35` **INFO** Running tsc --noEmit on api… +- `08:27:37` **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/api/src/db/schema.ts b/apps/api/src/db/schema.ts index ab3f439..a3d391b 100644 --- a/apps/api/src/db/schema.ts +++ b/apps/api/src/db/schema.ts @@ -95,15 +95,17 @@ export const timeEntryTemplates = pgTable("time_entry_templates", { userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }), name: text("name").notNull(), description: text("description"), - projectId: uuid("project_id").references(() => projects.id, { onDelete: "set null" }), + projectId: uuid("project_id").references(() => projects.id), createdAt: timestamp("created_at").notNull().defaultNow() }) export const appSettings = pgTable("app_settings", { id: uuid("id").primaryKey().defaultRandom(), - key: text("key").notNull().unique(), - value: text("value").notNull(), - roundingMinutes: integer("rounding_minutes").default(0), + workspaceName: text("workspace_name").notNull(), + logoUrl: text("logo_url"), + defaultBillable: boolean("default_billable").notNull().default(true), + weekStart: integer("week_start").notNull().default(1), + roundingMinutes: integer("rounding_minutes").notNull().default(0), updatedAt: timestamp("updated_at").notNull().defaultNow() }) @@ -130,8 +132,8 @@ export const documents = pgTable("documents", { export const webhooks = pgTable("webhooks", { id: uuid("id").primaryKey().defaultRandom(), url: text("url").notNull(), - secret: text("secret").notNull(), events: text("events").array().notNull(), + secret: text("secret").notNull(), active: boolean("active").notNull().default(true), createdAt: timestamp("created_at").notNull().defaultNow() }) @@ -140,7 +142,8 @@ export const savedViews = pgTable("saved_views", { id: uuid("id").primaryKey().defaultRandom(), userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), name: text("name").notNull(), - filters: text("filters").notNull(), + config: text("config").notNull(), + isDefault: boolean("is_default").notNull().default(false), createdAt: timestamp("created_at").notNull().defaultNow() }) diff --git a/apps/web/src/components/Nav.tsx b/apps/web/src/components/Nav.tsx index ca2708d..9e7a90a 100644 --- a/apps/web/src/components/Nav.tsx +++ b/apps/web/src/components/Nav.tsx @@ -36,6 +36,11 @@ export default function Nav() { queryFn: api.getMe }) + const { data: settings } = useQuery({ + queryKey: ['settings'], + queryFn: api.getSettings + }) + const navItems = [ { label: "Dashboard", to: "/", icon: Home }, { label: "Time Entries", to: "/time-entries", icon: Clock }, @@ -90,9 +95,19 @@ export default function Nav() {
- EmberClone + {settings?.logoUrl ? ( + Logo + ) : ( + + EmberClone + + )}
@@ -136,7 +151,7 @@ export default function Nav() { {/* Mobile Menu */} {isMobileMenuOpen && ( -
+
{allItems.map((item) => ( ))} diff --git a/scripts/phase23_features.py b/scripts/phase23_features.py new file mode 100644 index 0000000..f10eb1c --- /dev/null +++ b/scripts/phase23_features.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""Phase-23: workspace-logo, custom-themes, command-actions, drag-widgets, animations.""" + +from __future__ import annotations + +import asyncio, datetime, json, sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from phase2_features import Feature, FileGen, ROOT, log, log_section +from phase3_features import run_feature_v2 + +PHASE_STATE = ROOT / ".phase23-state.json" + +FEATURES: list[Feature] = [ + Feature( + name="workspace-logo", + description="Logo-Feld auf appSettings + Anzeige in Nav", + files=[FileGen( + path="apps/api/src/db/schema.ts", + purpose=( + "WICHTIG: BEHALTE ALLE existierenden Tabellen/Spalten. " + "Füge nur Spalte `logoUrl: text('logo_url')` (nullable) zu appSettings. " + "BEHALTE: users, customers, projects (mit icon, budgetHours, pinnedAt), projectTemplates, timeEntries (notes, externalLink), " + "timeEntryAttachments, timeEntryComments, appSettings (workspaceName, defaultBillable, weekStart, roundingMinutes), " + "auditLog, documents, webhooks, savedViews, apiKeys, passwordResetTokens, invitations." + ), + refs=["apps/api/src/db/schema.ts"], + ), FileGen( + path="apps/web/src/components/Nav.tsx", + purpose=( + "ERWEITERT — wenn appSettings.logoUrl set, zeige Logo links statt 'EmberClone'-Text. " + "Behalte alle bestehenden Nav-Links." + ), + refs=["apps/web/src/components/Nav.tsx"], + )], + ), + Feature( + name="custom-themes", + description="3 Color-Themes wählbar (Ember/Ocean/Forest)", + files=[FileGen( + path="apps/web/src/lib/theme.tsx", + purpose=( + "ERWEITERT — behalte light/dark toggle. Füge zusätzlich color-theme: 'ember' | 'ocean' | 'forest'. " + "Persist in localStorage 'colorTheme'. Setzt document.documentElement.dataset.colorTheme. " + "useTheme() returns auch {colorTheme, setColorTheme}." + ), + refs=["apps/web/src/lib/theme.tsx"], + ), FileGen( + path="apps/web/src/index.css", + purpose=( + "ERWEITERT — behalte alles. Füge CSS-vars für color-themes: " + "html[data-color-theme='ember'] { --primary: #f97316 } " + "html[data-color-theme='ocean'] { --primary: #0ea5e9 } " + "html[data-color-theme='forest'] { --primary: #10b981 }" + ), + refs=["apps/web/src/index.css"], + )], + ), + Feature( + name="command-bar-actions", + description="CommandPalette mit Aktionen (z.B. 'New TimeEntry', 'Toggle Dark')", + files=[FileGen( + path="apps/web/src/components/CommandPalette.tsx", + purpose=( + "ERWEITERT — behalte bestehende Navigation-Items. Füge actions section: " + "'Neuer Time-Entry' (öffnet QuickAdd), 'Dark/Light umschalten', 'Theme wechseln', 'Logout'. " + "Fuzzy-Filter auch über actions." + ), + refs=["apps/web/src/components/CommandPalette.tsx"], + )], + ), + Feature( + name="animated-transitions", + description="Page-Transitions mit fade-in beim Route-Change", + files=[FileGen( + path="apps/web/src/index.css", + purpose=( + "ERWEITERT — füge fade-in animation utility: " + "@keyframes fade-in { from {opacity:0; transform:translateY(4px)} to {opacity:1; transform:translateY(0)} } " + ".page-enter { animation: fade-in 200ms ease-out }. Behalte alles." + ), + refs=["apps/web/src/index.css"], + ), FileGen( + path="apps/web/src/App.tsx", + purpose=( + "ERWEITERT — wrap in
. " + "Force-rerender bei Route-Change. Behalte alles." + ), + refs=["apps/web/src/App.tsx"], + )], + ), + Feature( + name="drag-resize-widgets", + description="Dashboard-Widgets: resizable via drag-Handle (CSS-resize)", + files=[FileGen( + path="apps/web/src/pages/Dashboard.tsx", + purpose=( + "ERWEITERT — behalte alles. Jede Widget-Card bekommt `resize: both; overflow: auto; min-h-64` Style. " + "User kann unten-rechts ziehen." + ), + refs=["apps/web/src/pages/Dashboard.tsx"], + )], + ), +] + + +def load_state(): + if PHASE_STATE.exists(): + return json.loads(PHASE_STATE.read_text()) + return {"completed_features": [], "current_feature": None, "started_at": datetime.datetime.now().isoformat()} + + +def save_state(state): + PHASE_STATE.write_text(json.dumps(state, indent=2)) + + +async def main(): + log_section("🚀 Phase-23 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) + state.setdefault("completed_features" if success else "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-23 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()))