| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 7 | 32 | 0% |
| Commands | 0 | 3 | 0 | 0% |
| Section tags | 2 | 3 | 3 | 25% |
What each file covers
Sections
0 shared · 7 only in A · 32 only in B- − AGENTS.md
- − Shared Project Memory
- − Testing
- − Cloud LLM Model Maintenance
- − Feature Index
- − Security
- − Git
- + Configuration Management
- + ConfigManager Pattern
- + Singleton Access
- + Configuration Keys
- + Getter/Setter Pattern
- + Standard Pattern
- + Typed Getters
- + Configuration Files
- + File Structure
- + Loading Configuration
- + Saving Configuration
- + Service-Specific Configuration
- + Separate Config Files
- + Loading Service Configs
- + Default Values
- + Providing Defaults
- + Settings Window Integration
- + Binding Pattern
- + Two-Way Binding
- + Window Position Persistence
- + Saving Window State
- + Loading Window State
- + API Key Security
- + Storage
- + Masking in Logs
- + Configuration Validation
- + Validation Pattern
- + Migration and Backwards Compatibility
- + Handling Config Changes
- + Removing Old Settings
- + Configuration Constants
- + Supported Values
Commands
0 shared · 3 only in A · 0 only in B- − dotnet build .\UGTLive.sln --configuration Release
- − git commit
- − git push
Section tags
2 shared · 3 only in A · 3 only in B- − test
- − git-pr
- − performance
- + architecture
- + database
- + api
- security
- do-not
Line diff
SethRobinson/UGTLive · AGENTS.md
@@ −1 @@
1# AGENTS.md
2
3Project operating instructions for AI assistants working in this repository.
4
5## Shared Project Memory
6
7- At the start of each new task or thread involving this repository, read this file before inspecting files, running commands, making a plan, or taking any other project action.
8- Treat follow-up replies in the same continuous task as part of that task. Do not reread this file unless the repository or working directory changes, this file is modified, or its instructions are no longer available in context.
9- Treat this file as the shared project memory for AI assistants.
10- Do not rely on vendor-specific, proprietary, or hidden memory systems for project facts, preferences, or operating instructions. (except to remember to ALWAYS read this file first before doing anything. Remember that.)
11- Update this file with important repo-specific information learned during work, including build commands, test commands, conventions, decisions, pitfalls, and current project preferences.
12- Keep this file accurate and current. Remove or correct stale, misleading, or incorrect information when discovered.
13- If information is temporary or uncertain, label it clearly rather than presenting it as permanent fact.
14
15Scope policy: this file holds cross-cutting rules, workflows, and gotchas that most sessions need, plus a feature index. Keep it around 30 KB. Feature deep-dives live in `docs/<topic>.md`: before working on a feature listed in the index, read its doc; when finishing feature work, update that doc and keep the index entry here to one or two lines (where it lives + the non-obvious constraint). Cross-cutting rules and new gotchas still land here directly. When a change makes anything stale, here or in a linked doc, update it in the same change.
16
17## Testing
18
19- When possible, design automated tests for new features and bug fixes.
20- Run relevant automated tests after finishing changes to guard against regressions.
21- If tests cannot be run or do not exist, state that clearly in the handoff and describe any manual verification performed.
22- Always finish project changes by building the Release configuration: `dotnet build .\UGTLive.sln --configuration Release`.
23- If a running UGTLive process prevents the Release build, capture its executable/command line, stop it, complete the build, and restart it afterward. Do not start UGTLive if it was not running before the build.
24
25Always add automation/test harnesses to test options/buttons/features as needed. Document them.
26
27## Cloud LLM Model Maintenance
28
29- Cloud and CLI model picker presets live in `src/SettingsWindow.xaml`; their fallback/default values live in `src/ConfigManager.cs`, `src/ConfigManager.Translation.cs`, and `src/SettingsWindow.TranslationSettings.cs`.
30- Subscription-backed CLI providers display as `Anthropic Sub`, `OpenAI Sub`, and `Gemini CLI (Enterprise)`, but their stable internal IDs remain `ClaudeCli`, `CodexCli`, and `GeminiCli`; use `ComboBoxItem.Tag` for the internal ID. Google ended personal/free/AI Pro/AI Ultra access through Gemini CLI on June 18, 2026; do not replace it with Antigravity CLI until `agy -p` reliably exposes captured stdout to Windows parent processes (see `docs/settings-connection-tests.md`).
31- Keep provider-specific capability handling in the matching translation service. In particular, Anthropic model generations use different manual/adaptive thinking request shapes.
32- Verify model IDs and request compatibility against current official provider documentation. Verify OpenRouter-prefixed slugs against its `/api/v1/models` catalog before adding presets.
33
34## Feature Index
35
36- Settings API/model/voice tests: see `docs/settings-connection-tests.md`. UI buttons and `--test-settings-connection` must continue to call the shared `SettingsConnectionTester` implementation.
37- OpenAI All In One Snap translation: see `docs/openai-all-in-one.md`. It is a Snap-only, visual-only `gpt-image-2` Image Edits path; Auto and realtime processing must remain on the standard OCR pipeline.
38
39
40## Security
41
42- Never commit sensitive data, including credentials, tokens, passwords, private keys, cookies, customer data, personal data, or machine-specific authentication material.
43- If an AI assistant needs authentication data or other secrets for local work, use `agents_secret.md` for those notes.
44- `agents_secret.md` must stay ignored by git and must not be committed.
45- Do not put secrets in commit messages, logs, issue text, pull request descriptions, generated docs, or other tracked files.
46- Configuration logging must pass key names through `ConfigManager.IsSensitiveConfigKey`; never print raw secret values in startup or harness output.
47- Before committing, review staged changes for accidental secrets.
48
49## Git
50
51- Never add OpenAI/Codex/Claude etc as a co-author on git commits.
52- NEVER `git commit` unless explicitly told to commit.
53- NEVER `git push` unless explicitly told to push. "Commit" means commit
54 locally only; committing is not permission to push.
55
SethRobinson/UGTLive · .cursor/rules/configuration-management.mdc
@@ +1 @@
1---
2description: Configuration and settings management patterns
3globs: ["**/ConfigManager.cs", "**/SettingsWindow*.cs"]
4alwaysApply: false
5---
6
7# Configuration Management
8
9## ConfigManager Pattern
10
11### Singleton Access
12Always access ConfigManager through the singleton instance:
13
14```csharp
15var apiKey = ConfigManager.Instance.GetGeminiApiKey();
16ConfigManager.Instance.SetGeminiApiKey("new-key");
17```
18
19### Configuration Keys
20- Define keys as `public const string` constants
21- Use descriptive UPPER_SNAKE_CASE names
22- Group related keys together
23
24```csharp
25public const string GEMINI_API_KEY = "gemini_api_key";
26public const string GEMINI_MODEL = "gemini_model";
27public const string TRANSLATION_SERVICE = "translation_service";
28```
29
30## Getter/Setter Pattern
31
32### Standard Pattern
33For each configuration value, provide Get/Set methods:
34
35```csharp
36// Get method
37public string GetGeminiApiKey()
38{
39 return GetValue(GEMINI_API_KEY);
40}
41
42// Set method (auto-saves)
43public void SetGeminiApiKey(string apiKey)
44{
45 _configValues[GEMINI_API_KEY] = apiKey;
46 SaveConfig();
47}
48```
49
50### Typed Getters
51Provide typed getters for common types:
52
53```csharp
54// Boolean
55public bool GetBoolValue(string key, bool defaultValue = false)
56{
57 if (_configValues.TryGetValue(key, out var value))
58 {
59 return value.ToLower() == "true" || value == "1";
60 }
61 return defaultValue;
62}
63
64// Integer
65public int GetIntValue(string key, int defaultValue = 0)
66{
67 if (_configValues.TryGetValue(key, out var value))
68 {
69 if (int.TryParse(value, out int result))
70 {
71 return result;
72 }
73 }
74 return defaultValue;
75}
76
77// Double/Float
78public double GetDoubleValue(string key, double defaultValue = 0.0)
79{
80 if (_configValues.TryGetValue(key, out var value))
81 {
82 if (double.TryParse(value, out double result))
83 {
84 return result;
85 }
86 }
87 return defaultValue;
88}
89```
90
91## Configuration Files
92
93### File Structure
94- Main config: `config.txt` (in app directory)
95- Service-specific configs: `gemini_config.txt`, `ollama_config.txt`, etc.
96- Format: `key=value` pairs, one per line
97- Multiline values use tags: `<tag>content</tag>`
98
99### Loading Configuration
100```csharp
101private void LoadConfig()
102{
103 if (!File.Exists(_configFilePath))
104 {
105 CreateDefaultConfig();
106 return;
107 }
108
109 string content = File.ReadAllText(_configFilePath);
110 ProcessMultilineValues(content);
111 ProcessSingleLineValues(content);
112}
113```
114
115### Saving Configuration
116```csharp
117public void SaveConfig()
118{
119 try
120 {
121 var lines = new List<string>();
122 foreach (var kvp in _configValues)
123 {
124 if (kvp.Value.Contains('\n'))
125 {
126 // Multiline value
127 lines.Add($"<{kvp.Key}>");
128 lines.Add(kvp.Value);
129 lines.Add($"</{kvp.Key}>");
130 }
131 else
132 {
133 lines.Add($"{kvp.Key}={kvp.Value}");
134 }
135 }
136 File.WriteAllLines(_configFilePath, lines);
137 }
138 catch (Exception ex)
139 {
140 Console.WriteLine($"Error saving config: {ex.Message}");
141 }
142}
143```
144
145## Service-Specific Configuration
146
147### Separate Config Files
148Some services use separate config files for complex settings:
149
150- `gemini_config.txt` - Gemini prompt templates
151- `ollama_config.txt` - Ollama prompt templates
152- `chatgpt_config.txt` - ChatGPT prompt templates
153
154### Loading Service Configs
155```csharp
156public string GetGeminiPrompt()
157{
158 if (File.Exists(_geminiConfigFilePath))
159 {
160 return File.ReadAllText(_geminiConfigFilePath);
161 }
162 return GetDefaultGeminiPrompt();
163}
164```
165
166## Default Values
167
168### Providing Defaults
169Always provide sensible defaults:
170
171```csharp
172public string GetOllamaModel()
173{
174 return GetValue(OLLAMA_MODEL, "llama3"); // Default model
175}
176
177public int GetCaptureFPS()
178{
179 return GetIntValue(CAPTURE_FPS, 10); // Default 10 FPS
180}
181```
182
183## Settings Window Integration
184
185### Binding Pattern
186Settings window binds directly to ConfigManager:
187
188```csharp
189// In SettingsWindow.xaml.cs
190private void LoadSettings()
191{
192 ApiKeyTextBox.Text = ConfigManager.Instance.GetGeminiApiKey();
193 ModelComboBox.SelectedItem = ConfigManager.Instance.GetGeminiModel();
194}
195
196private void SaveSettings()
197{
198 ConfigManager.Instance.SetGeminiApiKey(ApiKeyTextBox.Text);
199 ConfigManager.Instance.SetGeminiModel(ModelComboBox.SelectedItem?.ToString() ?? "");
200}
201```
202
203### Two-Way Binding
204For real-time updates, use two-way binding:
205
206```xml
207<TextBox Text="{Binding ApiKey, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
208```
209
210## Window Position Persistence
211
212### Saving Window State
213```csharp
214public void SetWindowPosition(string windowName, double left, double top, double width, double height)
215{
216 SetValue($"{windowName}_left", left.ToString());
217 SetValue($"{windowName}_top", top.ToString());
218 SetValue($"{windowName}_width", width.ToString());
219 SetValue($"{windowName}_height", height.ToString());
220 SaveConfig();
221}
222```
223
224### Loading Window State
225```csharp
226public WindowPosition? GetWindowPosition(string windowName)
227{
228 var left = GetDoubleValue($"{windowName}_left", -1);
229 var top = GetDoubleValue($"{windowName}_top", -1);
230
231 if (left < 0 || top < 0)
232 {
233 return null; // No saved position
234 }
235
236 return new WindowPosition
237 {
238 X = left,
239 Y = top,
240 Width = GetDoubleValue($"{windowName}_width", 800),
241 Height = GetDoubleValue($"{windowName}_height", 600)
242 };
243}
244```
245
246## API Key Security
247
248### Storage
249- API keys stored in plain text config files (local only)
250- Never log API keys (mask in console output)
251- Never commit API keys to version control
252
253### Masking in Logs
254```csharp
255Console.WriteLine($"API Key: {(key.Length > 0 ? "***" : "not set")}");
256```
257
258## Configuration Validation
259
260### Validation Pattern
261Validate settings before saving:
262
263```csharp
264public bool ValidateSettings()
265{
266 if (string.IsNullOrWhiteSpace(GetGeminiApiKey()))
267 {
268 MessageBox.Show("Gemini API key is required", "Validation Error");
269 return false;
270 }
271
272 if (!IsValidModel(GetGeminiModel()))
273 {
274 MessageBox.Show("Invalid model selected", "Validation Error");
275 return false;
276 }
277
278 return true;
279}
280```
281
282## Migration and Backwards Compatibility
283
284### Handling Config Changes
285When adding new settings, provide defaults:
286
287```csharp
288// New setting with default
289public string GetNewSetting()
290{
291 return GetValue(NEW_SETTING_KEY, "default-value");
292}
293```
294
295### Removing Old Settings
296Clean up deprecated settings:
297
298```csharp
299// Remove old key if it exists
300if (_configValues.ContainsKey("old_key"))
301{
302 _configValues.Remove("old_key");
303 SaveConfig();
304}
305```
306
307## Configuration Constants
308
309### Supported Values
310Define supported values as constants:
311
312```csharp
313public static readonly IReadOnlyList<string> SupportedOcrMethods = new List<string>
314{
315 "EasyOCR",
316 "Manga OCR",
317 "docTR",
318 "Windows OCR"
319};
320
321public static bool IsSupportedOcrMethod(string method)
322{
323 return SupportedOcrMethods.Contains(method, StringComparer.OrdinalIgnoreCase);
324}
325```
326
@@ −1 +1 @@
1−# AGENTS.md
1+---
2+description: Configuration and settings management patterns
3+globs: ["**/ConfigManager.cs", "**/SettingsWindow*.cs"]
4+alwaysApply: false
5+---
26
3−Project operating instructions for AI assistants working in this repository.
7+# Configuration Management
48
5−## Shared Project Memory
9+## ConfigManager Pattern
610
7−- At the start of each new task or thread involving this repository, read this file before inspecting files, running commands, making a plan, or taking any other project action.
8−- Treat follow-up replies in the same continuous task as part of that task. Do not reread this file unless the repository or working directory changes, this file is modified, or its instructions are no longer available in context.
9−- Treat this file as the shared project memory for AI assistants.
10−- Do not rely on vendor-specific, proprietary, or hidden memory systems for project facts, preferences, or operating instructions. (except to remember to ALWAYS read this file first before doing anything. Remember that.)
11−- Update this file with important repo-specific information learned during work, including build commands, test commands, conventions, decisions, pitfalls, and current project preferences.
12−- Keep this file accurate and current. Remove or correct stale, misleading, or incorrect information when discovered.
13−- If information is temporary or uncertain, label it clearly rather than presenting it as permanent fact.
11+### Singleton Access
12+Always access ConfigManager through the singleton instance:
1413
15−Scope policy: this file holds cross-cutting rules, workflows, and gotchas that most sessions need, plus a feature index. Keep it around 30 KB. Feature deep-dives live in `docs/<topic>.md`: before working on a feature listed in the index, read its doc; when finishing feature work, update that doc and keep the index entry here to one or two lines (where it lives + the non-obvious constraint). Cross-cutting rules and new gotchas still land here directly. When a change makes anything stale, here or in a linked doc, update it in the same change.
14+```csharp
15+var apiKey = ConfigManager.Instance.GetGeminiApiKey();
16+ConfigManager.Instance.SetGeminiApiKey("new-key");
17+```
1618
17−## Testing
19+### Configuration Keys
20+- Define keys as `public const string` constants
21+- Use descriptive UPPER_SNAKE_CASE names
22+- Group related keys together
1823
19−- When possible, design automated tests for new features and bug fixes.
20−- Run relevant automated tests after finishing changes to guard against regressions.
21−- If tests cannot be run or do not exist, state that clearly in the handoff and describe any manual verification performed.
22−- Always finish project changes by building the Release configuration: `dotnet build .\UGTLive.sln --configuration Release`.
23−- If a running UGTLive process prevents the Release build, capture its executable/command line, stop it, complete the build, and restart it afterward. Do not start UGTLive if it was not running before the build.
24+```csharp
25+public const string GEMINI_API_KEY = "gemini_api_key";
26+public const string GEMINI_MODEL = "gemini_model";
27+public const string TRANSLATION_SERVICE = "translation_service";
28+```
2429
25−Always add automation/test harnesses to test options/buttons/features as needed. Document them.
30+## Getter/Setter Pattern
2631
27−## Cloud LLM Model Maintenance
32+### Standard Pattern
33+For each configuration value, provide Get/Set methods:
2834
29−- Cloud and CLI model picker presets live in `src/SettingsWindow.xaml`; their fallback/default values live in `src/ConfigManager.cs`, `src/ConfigManager.Translation.cs`, and `src/SettingsWindow.TranslationSettings.cs`.
30−- Subscription-backed CLI providers display as `Anthropic Sub`, `OpenAI Sub`, and `Gemini CLI (Enterprise)`, but their stable internal IDs remain `ClaudeCli`, `CodexCli`, and `GeminiCli`; use `ComboBoxItem.Tag` for the internal ID. Google ended personal/free/AI Pro/AI Ultra access through Gemini CLI on June 18, 2026; do not replace it with Antigravity CLI until `agy -p` reliably exposes captured stdout to Windows parent processes (see `docs/settings-connection-tests.md`).
31−- Keep provider-specific capability handling in the matching translation service. In particular, Anthropic model generations use different manual/adaptive thinking request shapes.
32−- Verify model IDs and request compatibility against current official provider documentation. Verify OpenRouter-prefixed slugs against its `/api/v1/models` catalog before adding presets.
35+```csharp
36+// Get method
37+public string GetGeminiApiKey()
38+{
39+ return GetValue(GEMINI_API_KEY);
40+}
3341
34−## Feature Index
42+// Set method (auto-saves)
43+public void SetGeminiApiKey(string apiKey)
44+{
45+ _configValues[GEMINI_API_KEY] = apiKey;
46+ SaveConfig();
47+}
48+```
3549
36−- Settings API/model/voice tests: see `docs/settings-connection-tests.md`. UI buttons and `--test-settings-connection` must continue to call the shared `SettingsConnectionTester` implementation.
37−- OpenAI All In One Snap translation: see `docs/openai-all-in-one.md`. It is a Snap-only, visual-only `gpt-image-2` Image Edits path; Auto and realtime processing must remain on the standard OCR pipeline.
50+### Typed Getters
51+Provide typed getters for common types:
3852
53+```csharp
54+// Boolean
55+public bool GetBoolValue(string key, bool defaultValue = false)
56+{
57+ if (_configValues.TryGetValue(key, out var value))
58+ {
59+ return value.ToLower() == "true" || value == "1";
60+ }
61+ return defaultValue;
62+}
3963
40−## Security
64+// Integer
65+public int GetIntValue(string key, int defaultValue = 0)
66+{
67+ if (_configValues.TryGetValue(key, out var value))
68+ {
69+ if (int.TryParse(value, out int result))
70+ {
71+ return result;
72+ }
73+ }
74+ return defaultValue;
75+}
4176
42−- Never commit sensitive data, including credentials, tokens, passwords, private keys, cookies, customer data, personal data, or machine-specific authentication material.
43−- If an AI assistant needs authentication data or other secrets for local work, use `agents_secret.md` for those notes.
44−- `agents_secret.md` must stay ignored by git and must not be committed.
45−- Do not put secrets in commit messages, logs, issue text, pull request descriptions, generated docs, or other tracked files.
46−- Configuration logging must pass key names through `ConfigManager.IsSensitiveConfigKey`; never print raw secret values in startup or harness output.
47−- Before committing, review staged changes for accidental secrets.
77+// Double/Float
78+public double GetDoubleValue(string key, double defaultValue = 0.0)
79+{
80+ if (_configValues.TryGetValue(key, out var value))
81+ {
82+ if (double.TryParse(value, out double result))
83+ {
84+ return result;
85+ }
86+ }
87+ return defaultValue;
88+}
89+```
4890
49−## Git
91+## Configuration Files
5092
51−- Never add OpenAI/Codex/Claude etc as a co-author on git commits.
52−- NEVER `git commit` unless explicitly told to commit.
53−- NEVER `git push` unless explicitly told to push. "Commit" means commit
54− locally only; committing is not permission to push.
93+### File Structure
94+- Main config: `config.txt` (in app directory)
95+- Service-specific configs: `gemini_config.txt`, `ollama_config.txt`, etc.
96+- Format: `key=value` pairs, one per line
97+- Multiline values use tags: `<tag>content</tag>`
98+
99+### Loading Configuration
100+```csharp
101+private void LoadConfig()
102+{
103+ if (!File.Exists(_configFilePath))
104+ {
105+ CreateDefaultConfig();
106+ return;
107+ }
108+
109+ string content = File.ReadAllText(_configFilePath);
110+ ProcessMultilineValues(content);
111+ ProcessSingleLineValues(content);
112+}
113+```
114+
115+### Saving Configuration
116+```csharp
117+public void SaveConfig()
118+{
119+ try
120+ {
121+ var lines = new List<string>();
122+ foreach (var kvp in _configValues)
123+ {
124+ if (kvp.Value.Contains('\n'))
125+ {
126+ // Multiline value
127+ lines.Add($"<{kvp.Key}>");
128+ lines.Add(kvp.Value);
129+ lines.Add($"</{kvp.Key}>");
130+ }
131+ else
132+ {
133+ lines.Add($"{kvp.Key}={kvp.Value}");
134+ }
135+ }
136+ File.WriteAllLines(_configFilePath, lines);
137+ }
138+ catch (Exception ex)
139+ {
140+ Console.WriteLine($"Error saving config: {ex.Message}");
141+ }
142+}
143+```
144+
145+## Service-Specific Configuration
146+
147+### Separate Config Files
148+Some services use separate config files for complex settings:
149+
150+- `gemini_config.txt` - Gemini prompt templates
151+- `ollama_config.txt` - Ollama prompt templates
152+- `chatgpt_config.txt` - ChatGPT prompt templates
153+
154+### Loading Service Configs
155+```csharp
156+public string GetGeminiPrompt()
157+{
158+ if (File.Exists(_geminiConfigFilePath))
159+ {
160+ return File.ReadAllText(_geminiConfigFilePath);
161+ }
162+ return GetDefaultGeminiPrompt();
163+}
164+```
165+
166+## Default Values
167+
168+### Providing Defaults
169+Always provide sensible defaults:
170+
171+```csharp
172+public string GetOllamaModel()
173+{
174+ return GetValue(OLLAMA_MODEL, "llama3"); // Default model
175+}
176+
177+public int GetCaptureFPS()
178+{
179+ return GetIntValue(CAPTURE_FPS, 10); // Default 10 FPS
180+}
181+```
182+
183+## Settings Window Integration
184+
185+### Binding Pattern
186+Settings window binds directly to ConfigManager:
187+
188+```csharp
189+// In SettingsWindow.xaml.cs
190+private void LoadSettings()
191+{
192+ ApiKeyTextBox.Text = ConfigManager.Instance.GetGeminiApiKey();
193+ ModelComboBox.SelectedItem = ConfigManager.Instance.GetGeminiModel();
194+}
195+
196+private void SaveSettings()
197+{
198+ ConfigManager.Instance.SetGeminiApiKey(ApiKeyTextBox.Text);
199+ ConfigManager.Instance.SetGeminiModel(ModelComboBox.SelectedItem?.ToString() ?? "");
200+}
201+```
202+
203+### Two-Way Binding
204+For real-time updates, use two-way binding:
205+
206+```xml
207+<TextBox Text="{Binding ApiKey, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
208+```
209+
210+## Window Position Persistence
211+
212+### Saving Window State
213+```csharp
214+public void SetWindowPosition(string windowName, double left, double top, double width, double height)
215+{
216+ SetValue($"{windowName}_left", left.ToString());
217+ SetValue($"{windowName}_top", top.ToString());
218+ SetValue($"{windowName}_width", width.ToString());
219+ SetValue($"{windowName}_height", height.ToString());
220+ SaveConfig();
221+}
222+```
223+
224+### Loading Window State
225+```csharp
226+public WindowPosition? GetWindowPosition(string windowName)
227+{
228+ var left = GetDoubleValue($"{windowName}_left", -1);
229+ var top = GetDoubleValue($"{windowName}_top", -1);
230+
231+ if (left < 0 || top < 0)
232+ {
233+ return null; // No saved position
234+ }
235+
236+ return new WindowPosition
237+ {
238+ X = left,
239+ Y = top,
240+ Width = GetDoubleValue($"{windowName}_width", 800),
241+ Height = GetDoubleValue($"{windowName}_height", 600)
242+ };
243+}
244+```
245+
246+## API Key Security
247+
248+### Storage
249+- API keys stored in plain text config files (local only)
250+- Never log API keys (mask in console output)
251+- Never commit API keys to version control
252+
253+### Masking in Logs
254+```csharp
255+Console.WriteLine($"API Key: {(key.Length > 0 ? "***" : "not set")}");
256+```
257+
258+## Configuration Validation
259+
260+### Validation Pattern
261+Validate settings before saving:
262+
263+```csharp
264+public bool ValidateSettings()
265+{
266+ if (string.IsNullOrWhiteSpace(GetGeminiApiKey()))
267+ {
268+ MessageBox.Show("Gemini API key is required", "Validation Error");
269+ return false;
270+ }
271+
272+ if (!IsValidModel(GetGeminiModel()))
273+ {
274+ MessageBox.Show("Invalid model selected", "Validation Error");
275+ return false;
276+ }
277+
278+ return true;
279+}
280+```
281+
282+## Migration and Backwards Compatibility
283+
284+### Handling Config Changes
285+When adding new settings, provide defaults:
286+
287+```csharp
288+// New setting with default
289+public string GetNewSetting()
290+{
291+ return GetValue(NEW_SETTING_KEY, "default-value");
292+}
293+```
294+
295+### Removing Old Settings
296+Clean up deprecated settings:
297+
298+```csharp
299+// Remove old key if it exists
300+if (_configValues.ContainsKey("old_key"))
301+{
302+ _configValues.Remove("old_key");
303+ SaveConfig();
304+}
305+```
306+
307+## Configuration Constants
308+
309+### Supported Values
310+Define supported values as constants:
311+
312+```csharp
313+public static readonly IReadOnlyList<string> SupportedOcrMethods = new List<string>
314+{
315+ "EasyOCR",
316+ "Manga OCR",
317+ "docTR",
318+ "Windows OCR"
319+};
320+
321+public static bool IsSupportedOcrMethod(string method)
322+{
323+ return SupportedOcrMethods.Contains(method, StringComparer.OrdinalIgnoreCase);
324+}
325+```
55326
