From 04c63d4ef601876186e5d7fab980d76575c494ec Mon Sep 17 00:00:00 2001 From: bndw Date: Sat, 28 Feb 2026 21:08:40 -0800 Subject: feat: **1. `src/main/db/schema.ts`** — add `settings` table … (+10 more) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ✅ **1. `src/main/db/schema.ts`** — add `settings` table to `initSchema` - ✅ **2. `src/main/db/settings.ts`** — create file with `getSetting`, `getSettings`, `setSetting`, `deleteSetting` - ✅ **3. `src/main/claude/phases.ts`** — add `customSystemPrompt?` param to `getPhaseConfig`; add `getDefaultSystemPromptTemplate` export - ✅ **4. `src/main/claude/index.ts`** — import `getSetting`; load custom prompt in `sendMessage`; pass to `getPhaseConfig` - ✅ **5. `src/main/ipc/handlers.ts`** — import `settingsDb` + `getDefaultSystemPromptTemplate`; register `settings:get`, `settings:set`, `settings:delete`, `settings:getDefaultPrompts` - ✅ **6. `src/main/preload.ts`** — add `getSettings`, `setSetting`, `deleteSetting`, `getDefaultSystemPrompts` to interface + api object - ✅ **7. `renderer/src/styles/globals.css`** — append all new CSS rules - ✅ **8. `renderer/src/components/settings/SystemPromptsSettings.tsx`** — create file (new directory) - ✅ **9. `renderer/src/components/SettingsPage.tsx`** — create file - ✅ **10. `renderer/src/components/Header.tsx`** — add `onOpenSettings` prop + ⚙ button - ✅ **11. `renderer/src/App.tsx`** — add `showSettings` state; import + render ``; pass `onOpenSettings` to Header --- renderer/src/App.tsx | 7 + renderer/src/components/Header.tsx | 7 + renderer/src/components/SettingsPage.tsx | 51 +++++ .../components/settings/SystemPromptsSettings.tsx | 142 ++++++++++++++ renderer/src/styles/globals.css | 213 +++++++++++++++++++++ src/main/claude/index.ts | 7 +- src/main/claude/phases.ts | 22 ++- src/main/db/schema.ts | 5 + src/main/db/settings.ts | 28 +++ src/main/ipc/handlers.ts | 20 ++ src/main/preload.ts | 12 ++ 11 files changed, 511 insertions(+), 3 deletions(-) create mode 100644 renderer/src/components/SettingsPage.tsx create mode 100644 renderer/src/components/settings/SystemPromptsSettings.tsx create mode 100644 src/main/db/settings.ts diff --git a/renderer/src/App.tsx b/renderer/src/App.tsx index 74b1f91..7d75196 100644 --- a/renderer/src/App.tsx +++ b/renderer/src/App.tsx @@ -3,6 +3,7 @@ import { Header } from "./components/Header"; import { DocumentPane } from "./components/DocumentPane"; import { ChatPane } from "./components/ChatPane"; import { ActionBar } from "./components/ActionBar"; +import { SettingsPage } from "./components/SettingsPage"; import type { Project, Session, Message, Phase, TokenUsage } from "./types"; import "./styles/globals.css"; @@ -56,6 +57,7 @@ export function App() { outputTokens: 0, }); const [error, setError] = useState(null); + const [showSettings, setShowSettings] = useState(false); const [theme, setTheme] = useState( () => (localStorage.getItem("cf-theme") as Theme) ?? "dark" @@ -365,6 +367,7 @@ export function App() { theme={theme} onToggleTheme={handleToggleTheme} gitBranch={selectedSession?.git_branch ?? null} + onOpenSettings={() => setShowSettings(true)} />
@@ -413,6 +416,10 @@ export function App() { }} disabled={!selectedSession} /> + + {showSettings && ( + setShowSettings(false)} /> + )}
); } diff --git a/renderer/src/components/Header.tsx b/renderer/src/components/Header.tsx index fc0289d..3a530d3 100644 --- a/renderer/src/components/Header.tsx +++ b/renderer/src/components/Header.tsx @@ -18,6 +18,7 @@ interface HeaderProps { theme: Theme; onToggleTheme: () => void; gitBranch: string | null; + onOpenSettings: () => void; } const phaseLabels: Record = { @@ -43,6 +44,7 @@ export function Header({ theme, onToggleTheme, gitBranch, + onOpenSettings, }: HeaderProps) { const handleDeleteProject = () => { if (!selectedProject || !onDeleteProject) return; @@ -222,6 +224,11 @@ export function Header({ + + {/* ── Settings button ── */} + ); diff --git a/renderer/src/components/SettingsPage.tsx b/renderer/src/components/SettingsPage.tsx new file mode 100644 index 0000000..5267665 --- /dev/null +++ b/renderer/src/components/SettingsPage.tsx @@ -0,0 +1,51 @@ +import React, { useState } from "react"; +import { SystemPromptsSettings } from "./settings/SystemPromptsSettings"; + +type SettingsSection = "system-prompts"; + +interface SettingsPageProps { + onClose: () => void; +} + +export function SettingsPage({ onClose }: SettingsPageProps) { + const [activeSection, setActiveSection] = + useState("system-prompts"); + + return ( +
+ {/* Header — matches the main app header height/style */} +
+
+ \u2699 Settings +
+ +
+ +
+ {/* Side nav */} + + + {/* Content */} +
+ {activeSection === "system-prompts" && } +
+
+
+ ); +} diff --git a/renderer/src/components/settings/SystemPromptsSettings.tsx b/renderer/src/components/settings/SystemPromptsSettings.tsx new file mode 100644 index 0000000..a0c6a22 --- /dev/null +++ b/renderer/src/components/settings/SystemPromptsSettings.tsx @@ -0,0 +1,142 @@ +import React, { useState, useEffect } from "react"; +import type { Phase } from "../../types"; + +const PHASES: Phase[] = ["research", "plan", "implement"]; +const PHASE_LABELS: Record = { + research: "Research", + plan: "Plan", + implement: "Implement", +}; + +const api = window.api; + +export function SystemPromptsSettings() { + const [activePhase, setActivePhase] = useState("research"); + // Hardcoded default text per phase (fetched once from main process) + const [defaults, setDefaults] = useState | null>(null); + // What is currently saved in the DB per phase (null = not customized) + const [saved, setSaved] = useState>({ + research: null, + plan: null, + implement: null, + }); + // Live textarea content per phase — initialised to saved ?? default on load + const [texts, setTexts] = useState>({ + research: "", + plan: "", + implement: "", + }); + const [saveStatus, setSaveStatus] = useState<"idle" | "saving" | "saved">("idle"); + + useEffect(() => { + Promise.all([ + api.getDefaultSystemPrompts(), + api.getSettings([ + "systemPrompt.research", + "systemPrompt.plan", + "systemPrompt.implement", + ]), + ]).then(([defs, settings]) => { + const d = defs as Record; + const s: Record = { + research: settings["systemPrompt.research"], + plan: settings["systemPrompt.plan"], + implement: settings["systemPrompt.implement"], + }; + setDefaults(d); + setSaved(s); + setTexts({ + research: s.research ?? d.research, + plan: s.plan ?? d.plan, + implement: s.implement ?? d.implement, + }); + }); + }, []); + + if (!defaults) { + return ( +
+ Loading... +
+ ); + } + + const currentText = texts[activePhase]; + const isCustomized = saved[activePhase] !== null; + // Dirty = textarea differs from what is in DB (or from default if not customized) + const isDirty = currentText !== (saved[activePhase] ?? defaults[activePhase]); + + const handleChange = (val: string) => { + setTexts(prev => ({ ...prev, [activePhase]: val })); + setSaveStatus("idle"); + }; + + const handleSave = async () => { + setSaveStatus("saving"); + await api.setSetting(`systemPrompt.${activePhase}`, currentText); + setSaved(prev => ({ ...prev, [activePhase]: currentText })); + setSaveStatus("saved"); + setTimeout(() => setSaveStatus("idle"), 2000); + }; + + const handleReset = async () => { + await api.deleteSetting(`systemPrompt.${activePhase}`); + setSaved(prev => ({ ...prev, [activePhase]: null })); + setTexts(prev => ({ ...prev, [activePhase]: defaults[activePhase] })); + setSaveStatus("idle"); + }; + + return ( +
+
System Prompts
+
+ Customize the instructions sent to Claude at the start of each workflow + phase. Changes take effect on the next message sent in any session. +
+ +
+ {PHASES.map(phase => ( + + ))} +
+ +