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
+9
View File
@@ -34,6 +34,7 @@ import 'package:clide/kernel/src/recent_files.dart';
import 'package:clide/kernel/src/scheduler.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/theme/loader.dart';
import 'package:clide/kernel/src/toolchain.dart';
@@ -81,10 +82,14 @@ class KernelServices {
required this.textZoom,
required this.toast,
required this.logRing,
required this.settingsRegistry,
});
final Logger log;
final SettingsStore settings;
/// Categories registered for the Settings panel (T-444).
final SettingsRegistry settingsRegistry;
final DaemonBus events;
final MessageBus messages;
@@ -155,6 +160,7 @@ class KernelServices {
final settings = SettingsStore(appDir: appDir, onError: (m) => log.warn('settings', m));
await settings.load();
final settingsRegistry = SettingsRegistry();
final i18n = I18n(loader: i18nLoader, log: log, defaultLocale: defaultLocale, initialLocale: initialLocale, availableLocales: availableLocales);
for (final ns in preloadNamespaces) {
@@ -238,6 +244,7 @@ class KernelServices {
focus: focus,
project: project,
ipc: ipc,
settingsRegistry: settingsRegistry,
);
if (autoStartDaemonClient) {
@@ -248,6 +255,7 @@ class KernelServices {
log: log,
logRing: logRing,
settings: settings,
settingsRegistry: settingsRegistry,
events: events,
messages: messages,
filterStates: filterStates,
@@ -287,6 +295,7 @@ class KernelServices {
await ipc.stop();
ipc.dispose();
settings.dispose();
settingsRegistry.dispose();
theme.dispose();
panels.dispose();
arrangement.dispose();