refactor(settings): ClideSettings — one live-preferences facade (D-101)
Introduce ClideSettings, a single widget-facing facade for the app's live user preferences, namespaced by concern: ClideSettings.fonts.monoOf(context) / .fonts.uiOf(context), ClideSettings.theme.of(context), ClideSettings.i18n.of(context). "Plumb once, use many." Fonts are carried by a new root-provided ClideSettingsScope (resolved from the font settings in root_shell, rebuilt on change); theme and i18n delegate to their existing live providers (ClideTheme / the I18n service) so there's one source of truth and their many consumers migrate incrementally rather than in a big-bang. Reads outside a scope fall back to the bundled font defaults, so a widget renders without a provider (isolated tests). Migrate ~93 monospace-font call sites across 33 files from the clideMonoFamily const to ClideSettings.fonts.monoOf(context) — pure refactor, identical family when no override is set. 11 context-less helper sites (markdown static spans, a few top-level/static builders) keep the const for now and are tracked in T-472. Records D-101; updates the ui-design skill's font-family rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'package:clide/kernel/src/theme/controller.dart';
|
||||
import 'package:clide/widgets/src/clide_icon.dart';
|
||||
import 'package:clide/widgets/src/clide_settings.dart';
|
||||
import 'package:clide/widgets/src/clide_tappable.dart';
|
||||
import 'package:clide/widgets/src/clide_text.dart';
|
||||
import 'package:clide/widgets/src/icons/phosphor.dart';
|
||||
@@ -43,7 +44,7 @@ class ClideAccordion extends StatelessWidget {
|
||||
'$label · $count',
|
||||
fontSize: clideFontSmall,
|
||||
color: hovered ? tokens.globalForeground : tokens.sidebarSectionHeader,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:clide/kernel/src/theme/controller.dart';
|
||||
import 'package:clide/widgets/src/typography.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'clide_settings.dart';
|
||||
|
||||
class ClideCodeBlock extends StatefulWidget {
|
||||
const ClideCodeBlock({super.key, required this.source, this.language});
|
||||
|
||||
@@ -50,7 +52,12 @@ class _ClideCodeBlockState extends State<ClideCodeBlock> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
final style = TextStyle(fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback, fontSize: clideFontMono, color: tokens.globalForeground);
|
||||
final style = TextStyle(
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
fontFamilyFallback: clideMonoFamilyFallback,
|
||||
fontSize: clideFontMono,
|
||||
color: tokens.globalForeground,
|
||||
);
|
||||
|
||||
final spans = _spans;
|
||||
TextSpan textSpan;
|
||||
|
||||
@@ -27,6 +27,7 @@ import 'package:clide/kernel/src/theme/controller.dart';
|
||||
import 'package:clide/kernel/src/theme/tokens.dart';
|
||||
import 'package:clide/widgets/src/clide_card_metrics.dart';
|
||||
import 'package:clide/widgets/src/clide_icon.dart';
|
||||
import 'package:clide/widgets/src/clide_settings.dart';
|
||||
import 'package:clide/widgets/src/clide_status_indicator.dart';
|
||||
import 'package:clide/widgets/src/clide_tappable.dart';
|
||||
import 'package:clide/widgets/src/clide_text.dart';
|
||||
@@ -119,7 +120,7 @@ class _ClideCollapserCardState extends State<ClideCollapserCard> {
|
||||
// Chevron — hard against the left edge; the toggle.
|
||||
ClideIcon(expanded ? const ChevronDownIcon() : const ChevronRightIcon(), size: 12, color: accent),
|
||||
const SizedBox(width: 8),
|
||||
ClideText(widget.label, fontSize: clideFontCaption, fontFamily: clideMonoFamily, color: accent),
|
||||
ClideText(widget.label, fontSize: clideFontCaption, fontFamily: ClideSettings.fonts.monoOf(context), color: accent),
|
||||
if (showSummary) ...[
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
@@ -128,7 +129,7 @@ class _ClideCollapserCardState extends State<ClideCollapserCard> {
|
||||
// Summary one step below the label, matching ConversationCard so
|
||||
// collapser + tool cards read consistently in the stream (T-344).
|
||||
fontSize: clideFontMeta,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
color: tokens.globalTextMuted,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:clide/widgets/src/clide_settings.dart';
|
||||
import 'package:clide/widgets/src/clide_text.dart';
|
||||
import 'package:clide/widgets/src/typography.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -139,7 +140,7 @@ class _ClidePaletteState extends State<ClidePalette> {
|
||||
child: EditableText(
|
||||
controller: _input,
|
||||
focusNode: _focus,
|
||||
style: TextStyle(fontFamily: clideMonoFamily, fontSize: clideFontMono, color: tokens.dropdownForeground),
|
||||
style: TextStyle(fontFamily: ClideSettings.fonts.monoOf(context), fontSize: clideFontMono, color: tokens.dropdownForeground),
|
||||
cursorColor: tokens.globalFocus,
|
||||
backgroundCursorColor: tokens.globalFocus,
|
||||
maxLines: 1,
|
||||
@@ -222,7 +223,8 @@ class _PaletteItemState extends State<_PaletteItem> {
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: ClideText(widget.title, color: selected ? tokens.listItemSelectedForeground : tokens.listItemForeground)),
|
||||
if (widget.binding != null) ClideText(widget.binding!, fontSize: clideFontCaption, fontFamily: clideMonoFamily, color: tokens.globalTextMuted),
|
||||
if (widget.binding != null)
|
||||
ClideText(widget.binding!, fontSize: clideFontCaption, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.globalTextMuted),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:clide/widgets/src/typography.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:clide/src/terminal/terminal.dart';
|
||||
|
||||
import 'clide_settings.dart';
|
||||
|
||||
/// Theme-aware terminal view. Wraps xterm.dart's [TerminalView] with
|
||||
/// clide token bindings, JetBrainsMono as the face, and a Semantics
|
||||
/// wrapper that exposes the pane as a live region with the terminal's
|
||||
@@ -42,7 +44,7 @@ class ClidePtyView extends StatelessWidget {
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus,
|
||||
theme: _buildTheme(tokens),
|
||||
textStyle: TerminalStyle(fontSize: fontSize, fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback),
|
||||
textStyle: TerminalStyle(fontSize: fontSize, fontFamily: ClideSettings.fonts.monoOf(context), fontFamilyFallback: clideMonoFamilyFallback),
|
||||
padding: const EdgeInsets.all(2),
|
||||
backgroundOpacity: 1,
|
||||
cursorType: TerminalCursorType.block,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:clide/kernel/src/facade.dart' show ClideKernel;
|
||||
import 'package:clide/kernel/src/i18n/i18n.dart' show I18n;
|
||||
import 'package:clide/kernel/src/theme/controller.dart' show ClideTheme, ClideThemeData;
|
||||
import 'package:clide/widgets/src/typography.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// One facade for the app's live user preferences — fonts, theme, and i18n —
|
||||
/// read uniformly as `ClideSettings.fonts.monoOf(context)`,
|
||||
/// `ClideSettings.theme.of(context)`, `ClideSettings.i18n.of(context)`. Plumb
|
||||
/// once at the root, use many (D-101).
|
||||
///
|
||||
/// The values all originate in the kernel `SettingsStore`; this is the
|
||||
/// widget-facing read side. Fonts are carried by [ClideSettingsScope] (the root
|
||||
/// resolves them from the font settings and rebuilds on change, so dependents
|
||||
/// re-read live). Theme and i18n **delegate** to their existing live providers
|
||||
/// ([ClideTheme] / the `I18n` service) rather than duplicating them — one source
|
||||
/// of truth, and consumers migrate onto this facade incrementally. Reads
|
||||
/// outside a [ClideSettingsScope] fall back to the bundled font defaults, so a
|
||||
/// widget never needs the scope to render (isolated tests).
|
||||
abstract final class ClideSettings {
|
||||
static const fonts = _Fonts();
|
||||
static const theme = _Theme();
|
||||
static const i18n = _I18n();
|
||||
}
|
||||
|
||||
class _Fonts {
|
||||
const _Fonts();
|
||||
|
||||
/// The active monospace family in scope, else the bundled default.
|
||||
String monoOf(BuildContext context) => ClideSettingsScope.of(context)?.mono ?? clideMonoFamily;
|
||||
|
||||
/// The active UI family in scope, else the bundled default.
|
||||
String uiOf(BuildContext context) => ClideSettingsScope.of(context)?.ui ?? clideUiFamily;
|
||||
}
|
||||
|
||||
class _Theme {
|
||||
const _Theme();
|
||||
|
||||
/// Resolved theme data for [context] — delegates to the [ClideTheme] provider.
|
||||
ClideThemeData of(BuildContext context) => ClideTheme.of(context);
|
||||
}
|
||||
|
||||
class _I18n {
|
||||
const _I18n();
|
||||
|
||||
/// The i18n service for [context] — delegates to the kernel `I18n` service.
|
||||
I18n of(BuildContext context) => ClideKernel.of(context).i18n;
|
||||
}
|
||||
|
||||
/// Root-provided InheritedWidget carrying the live font families (D-101). The
|
||||
/// root rebuilds it from the font settings on change; font dependents re-read
|
||||
/// via [ClideSettings.fonts]. Theme/i18n have their own providers, so they are
|
||||
/// not duplicated here.
|
||||
class ClideSettingsScope extends InheritedWidget {
|
||||
const ClideSettingsScope({super.key, required this.ui, required this.mono, required super.child});
|
||||
|
||||
final String ui;
|
||||
final String mono;
|
||||
|
||||
static ClideSettingsScope? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<ClideSettingsScope>();
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ClideSettingsScope old) => ui != old.ui || mono != old.mono;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:clide/widgets/src/clide_settings.dart';
|
||||
import 'package:clide/widgets/src/clide_text.dart';
|
||||
import 'package:clide/widgets/src/typography.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -160,13 +161,13 @@ class _ExLineOverlayState extends State<ExLineOverlay> {
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: [
|
||||
ClideText(':', fontFamily: clideMonoFamily, color: tokens.globalTextMuted),
|
||||
ClideText(':', fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.globalTextMuted),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: EditableText(
|
||||
controller: _input,
|
||||
focusNode: _focus,
|
||||
style: TextStyle(fontFamily: clideMonoFamily, fontSize: clideFontMono, color: tokens.dropdownForeground),
|
||||
style: TextStyle(fontFamily: ClideSettings.fonts.monoOf(context), fontSize: clideFontMono, color: tokens.dropdownForeground),
|
||||
cursorColor: tokens.globalFocus,
|
||||
backgroundCursorColor: tokens.globalFocus,
|
||||
maxLines: 1,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:clide/widgets/src/clide_settings.dart';
|
||||
import 'package:clide/widgets/src/clide_text.dart';
|
||||
import 'package:clide/widgets/src/typography.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -187,7 +188,7 @@ class _QuickOpenOverlayState extends State<QuickOpenOverlay> {
|
||||
child: EditableText(
|
||||
controller: _input,
|
||||
focusNode: _focus,
|
||||
style: TextStyle(fontFamily: clideMonoFamily, fontSize: clideFontMono, color: tokens.dropdownForeground),
|
||||
style: TextStyle(fontFamily: ClideSettings.fonts.monoOf(context), fontSize: clideFontMono, color: tokens.dropdownForeground),
|
||||
cursorColor: tokens.globalFocus,
|
||||
backgroundCursorColor: tokens.globalFocus,
|
||||
maxLines: 1,
|
||||
@@ -288,7 +289,7 @@ class _QuickOpenItemState extends State<_QuickOpenItem> {
|
||||
child: ClideText(
|
||||
dir,
|
||||
fontSize: clideFontCaption,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
color: tokens.globalTextMuted,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
||||
Reference in New Issue
Block a user