feat(settings): schema-driven settings engine (T-448)

The core of the settings panel (epic T-444). Subsystems describe a category
as data — a SettingsCategory of carded SettingsSections of SettingsFields
(toggle / select / text / number / opens-external-file), each bound to a
SettingsStore key with help text, a default, and reset-to-default.

Registration is declarative: a new SettingsCategoryContribution carries the
category; the extension manager routes it into a new kernel SettingsRegistry
(exposed on KernelServices), which the panel reads via ClideKernel. Adding a
category is now pure data + a contribution — no widget code.

SettingsCategoryView renders a category into carded sections per ui-design
surface.md: panelHeader card fill, dividerColor border, inputs receding to
panelBackground; select reuses the anchored-overlay menu, text/number commit
on Enter or blur (numeric clamps to bounds). The modal panel now shows the
selected/first registered category, falling back to the empty state.

Tests: registry (sort / dedup / notify), contribution routing on activation,
renderer (render + toggle/select write-through + reset), modal-with-category.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 12:07:51 +02:00
co-authored by Claude Opus 4.8
parent 643f40d7b2
commit 4bbb0ee4b3
15 changed files with 890 additions and 22 deletions
+8
View File
@@ -25,6 +25,7 @@ import 'package:clide/kernel/src/panels/registry.dart';
import 'package:clide/kernel/src/project.dart';
import 'package:clide/kernel/src/secrets.dart';
import 'package:clide/kernel/src/settings.dart';
import 'package:clide/kernel/src/settings_registry.dart';
import 'package:clide/kernel/src/theme/controller.dart';
import 'package:clide/kernel/src/tray.dart';
import 'package:flutter/foundation.dart';
@@ -55,6 +56,7 @@ class ExtensionManager extends ChangeNotifier {
required this.focus,
required this.project,
required this.ipc,
required this.settingsRegistry,
});
final Logger log;
@@ -81,6 +83,7 @@ class ExtensionManager extends ChangeNotifier {
final FocusTracker focus;
final ProjectManager project;
final DaemonClient ipc;
final SettingsRegistry settingsRegistry;
final Map<String, ClideExtension> _known = {};
final Set<String> _activated = {};
@@ -256,6 +259,9 @@ class ExtensionManager extends ChangeNotifier {
// Presets are consumed by the default-layout extension in its
// own activate(); nothing for the kernel to do here.
break;
case SettingsCategoryContribution s:
// register() throws on a duplicate id, rolling activation back.
settingsRegistry.register(s.category);
}
}
@@ -276,6 +282,8 @@ class ExtensionManager extends ChangeNotifier {
tray.remove(t.id);
case LayoutPresetContribution _:
break;
case SettingsCategoryContribution s:
settingsRegistry.unregister(s.category.id);
}
}