/// Schema model for the settings panel (T-448, epic T-444). /// /// Pure data — no Flutter imports — so any subsystem can declare a category /// without depending on the widget layer. The settings-ui renderer turns a /// [SettingsCategory] into carded sections of field rows; each field binds to /// a `SettingsStore` key and is read/written through the store. library; /// The control a [SettingsField] renders as. enum SettingsFieldKind { /// On/off boolean. toggle, /// One value chosen from [SettingsField.options]. select, /// Free-text input. text, /// Numeric input (optionally bounded by [SettingsField.min]/[max]). number, /// A row that opens an external file/editor (e.g. `.editorconfig`) instead /// of editing a value inline — the action is a command id, keeping the /// schema widget-free. file, /// A bespoke control rendered by a widget the owning subsystem registers /// (via `SettingsControlContribution`) under [SettingsField.customId]. Keeps /// the schema widget-free while allowing the one-off controls the generic /// kinds can't express (e.g. the Appearance theme picker). custom, } /// One choice in a [SettingsFieldKind.select] field. class SettingsOption { const SettingsOption({required this.value, required this.label, this.labelKey}); /// Stored value. final String value; /// Human label shown in the picker (English fallback). final String label; /// Optional i18n key for [label], resolved in the owning category's namespace /// (T-462). final String? labelKey; } /// One editable setting. [key] is a `SettingsStore` key — its `app.`/ /// `project.`/`ext.` prefix determines the scope (and the per-field scope tag, /// T-449). The renderer reads the current value with `store.get`, falling back /// to [defaultValue] when unset, and writes edits with `store.set`. class SettingsField { const SettingsField({ required this.key, required this.kind, required this.label, this.labelKey, this.help, this.helpKey, this.defaultValue, this.options = const [], this.min, this.max, this.fileCommand, this.applyCommandPrefix, this.customId, }); final String key; final SettingsFieldKind kind; final String label; /// Optional i18n key for [label], resolved in the category's namespace (T-462). final String? labelKey; /// Optional one-line help shown under the label. final String? help; /// Optional i18n key for [help], resolved in the category's namespace (T-462). final String? helpKey; /// Value shown / restored when the key is unset (reset-to-default target). final Object? defaultValue; /// Choices for [SettingsFieldKind.select]. final List options; /// Optional inclusive bounds for [SettingsFieldKind.number]. final num? min; final num? max; /// For [SettingsFieldKind.file]: the command id the row's button invokes. final String? fileCommand; /// For [SettingsFieldKind.select]: when set, picking option `` runs /// the command `` instead of writing [key] /// directly — for settings a subsystem applies via a command (and only then /// persists). The current value is still read from [key], so the scope tag /// and selection still work. Example: `'keymap.preset.'` → `keymap.preset.vim`. final String? applyCommandPrefix; /// For [SettingsFieldKind.custom]: the id the renderer looks up in the /// `SettingsControlRegistry` to find the widget that draws this field. final String? customId; } /// A carded group of fields (surface.md "sectioned cards"). [label] is the /// small-caps header rendered just above the card. class SettingsSection { const SettingsSection({required this.label, required this.fields, this.labelKey}); final String label; /// Optional i18n key for [label], resolved in the category's namespace (T-462). final String? labelKey; final List fields; } /// One settings category — a rail entry (T-447) plus the sections its panel /// shows. Subsystems register these via `SettingsCategoryContribution`; the /// renderer draws them. class SettingsCategory { const SettingsCategory({required this.id, required this.title, required this.sections, this.iconName, this.priority = 0, this.titleKey, this.i18nNamespace}); final String id; final String title; /// Optional i18n key for [title] (the rail label), looked up in /// [i18nNamespace] (T-462). final String? titleKey; /// The i18n namespace for this category's [titleKey] and every section/field/ /// option key beneath it — usually the contributing extension's `id`. The /// renderer threads it down so the whole category localizes from one catalog. final String? i18nNamespace; /// Phosphor glyph name, resolved via `PhosphorIcons.byName` at render (T-314). final String? iconName; /// Rail ordering — lower sorts first; ties broken by [title]. final int priority; final List sections; }