summaryrefslogtreecommitdiff
path: root/internal/runtimeconfig/store.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-16 03:51:43 +0200
committerPaul Buetow <paul@buetow.org>2026-03-16 03:51:43 +0200
commitde3e878ad12bbd3e609bd5b7d741fc792c72f255 (patch)
tree06d92b93ea0ad532c5d3a761033baac05abe2a5e /internal/runtimeconfig/store.go
parent2e9cabb1c8bf1f0246e513fe1f86a552e07eee94 (diff)
Decompose App God struct into embedded section structs
Replace 60+ flat fields in App with 4 embedded section structs: CoreConfig, ProviderConfig, PromptConfig, FeatureConfig. Go field promotion preserves all existing field access patterns. Updated flattenAppConfig to recurse into embedded structs for runtimeconfig. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/runtimeconfig/store.go')
-rw-r--r--internal/runtimeconfig/store.go66
1 files changed, 44 insertions, 22 deletions
diff --git a/internal/runtimeconfig/store.go b/internal/runtimeconfig/store.go
index 4ee7ada..b8d34b4 100644
--- a/internal/runtimeconfig/store.go
+++ b/internal/runtimeconfig/store.go
@@ -118,38 +118,60 @@ func Diff(oldCfg, newCfg appconfig.App) []Change {
return changes
}
+// flattenAppConfig converts an App config into a flat key/value map for diffing.
+// It recurses into embedded structs (CoreConfig, ProviderConfig, etc.) to reach
+// all leaf fields. Keys are derived from json tags, with fallbacks for fields
+// that use json:"-" (e.g. surface configs, stats).
func flattenAppConfig(cfg appconfig.App) map[string]string {
result := make(map[string]string)
- val := reflect.ValueOf(cfg)
+ flattenStructFields(reflect.ValueOf(cfg), result)
+ return result
+}
+
+// flattenStructFields iterates over struct fields, recursing into anonymous
+// (embedded) structs and extracting key/value pairs from leaf fields.
+func flattenStructFields(val reflect.Value, result map[string]string) {
typ := val.Type()
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
- key := strings.TrimSpace(field.Tag.Get("toml"))
- if key == "" || key == "-" {
- switch field.Name {
- case "StatsWindowMinutes":
- key = "stats_window_minutes"
- case "CompletionConfigs":
- key = "completion_configs"
- case "CodeActionConfigs":
- key = "code_action_configs"
- case "ChatConfigs":
- key = "chat_configs"
- case "CLIConfigs":
- key = "cli_configs"
- default:
- continue
- }
- }
- if idx := strings.Index(key, ","); idx >= 0 {
- key = key[:idx]
+ // Recurse into embedded (anonymous) structs to flatten their fields.
+ if field.Anonymous && field.Type.Kind() == reflect.Struct {
+ flattenStructFields(val.Field(i), result)
+ continue
}
- if key == "" || key == "-" {
+ key := fieldKey(field)
+ if key == "" {
continue
}
result[key] = stringifyValue(val.Field(i))
}
- return result
+}
+
+// fieldKey derives the flattened map key for a struct field from its json tag,
+// with manual fallbacks for fields tagged json:"-" that still need tracking.
+func fieldKey(field reflect.StructField) string {
+ key := strings.TrimSpace(field.Tag.Get("json"))
+ if key == "" || key == "-" {
+ // Manual fallbacks for fields hidden from JSON but needed in diffs.
+ switch field.Name {
+ case "StatsWindowMinutes":
+ return "stats_window_minutes"
+ case "CompletionConfigs":
+ return "completion_configs"
+ case "CodeActionConfigs":
+ return "code_action_configs"
+ case "ChatConfigs":
+ return "chat_configs"
+ case "CLIConfigs":
+ return "cli_configs"
+ default:
+ return ""
+ }
+ }
+ if idx := strings.Index(key, ","); idx >= 0 {
+ key = key[:idx]
+ }
+ return key
}
func stringifyValue(v reflect.Value) string {