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:
2026-06-17 20:57:16 +02:00
co-authored by Claude Opus 4.8
parent 8ad5c9ba6e
commit a7ea38acde
44 changed files with 452 additions and 206 deletions
+18 -13
View File
@@ -105,7 +105,7 @@ class _TipsCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClideText('TIPS', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
ClideText('TIPS', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: ClideSettings.fonts.monoOf(context)),
const SizedBox(height: 14),
_tipRow(firstRow),
const SizedBox(height: 8),
@@ -170,7 +170,7 @@ class _StartColumn extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClideText('START', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
ClideText('START', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: ClideSettings.fonts.monoOf(context)),
const SizedBox(height: 20),
// Only flows that exist get a tile — the old Clone-from-git and
// Start-a-Claude-session rows were inert and advertised shortcuts
@@ -233,7 +233,7 @@ class _ActionRow extends StatelessWidget {
Expanded(
child: ClideText(label, fontSize: clideFontBody, color: tokens.globalForeground),
),
if (shortcut != null) ClideText(shortcut!, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
if (shortcut != null) ClideText(shortcut!, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context)),
],
),
),
@@ -255,7 +255,7 @@ class _RecentColumn extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClideText('RECENT', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
ClideText('RECENT', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: ClideSettings.fonts.monoOf(context)),
const SizedBox(height: 20),
if (recents.isEmpty)
const ClideText('No recent projects.', muted: true, fontSize: clideFontCaption)
@@ -309,7 +309,7 @@ class _RecentRow extends StatelessWidget {
project.relativePath,
muted: true,
fontSize: clideFontMeta,
fontFamily: clideMonoFamily,
fontFamily: ClideSettings.fonts.monoOf(context),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -323,7 +323,7 @@ class _RecentRow extends StatelessWidget {
project.branch!,
muted: true,
fontSize: clideFontMeta,
fontFamily: clideMonoFamily,
fontFamily: ClideSettings.fonts.monoOf(context),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -399,17 +399,17 @@ class _StatusLine extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
ClideText('$clideName $clideVersion', muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily),
ClideText('$clideName $clideVersion', muted: true, fontSize: clideFontSmall, fontFamily: ClideSettings.fonts.monoOf(context)),
ClideText(' · ', muted: true, fontSize: clideFontSmall),
if (!tc.resolved)
ClideText('checking…', muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily)
ClideText('checking…', muted: true, fontSize: clideFontSmall, fontFamily: ClideSettings.fonts.monoOf(context))
else if (tc.allOk)
ClideText('application ok', fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: tokens.statusSuccess)
ClideText('application ok', fontSize: clideFontSmall, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.statusSuccess)
else
ClideText(
tc.missing.map((t) => '$t not found').join(' · '),
fontSize: clideFontSmall,
fontFamily: clideMonoFamily,
fontFamily: ClideSettings.fonts.monoOf(context),
color: tokens.statusWarning,
),
ClideText(' · ', muted: true, fontSize: clideFontSmall),
@@ -435,8 +435,13 @@ class _ThemeLink extends StatelessWidget {
builder: (ctx, hovered, _) => Row(
mainAxisSize: MainAxisSize.min,
children: [
ClideText('theme: ', muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily),
ClideText(themeName, fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: hovered ? tokens.globalForeground : tokens.globalFocus),
ClideText('theme: ', muted: true, fontSize: clideFontSmall, fontFamily: ClideSettings.fonts.monoOf(context)),
ClideText(
themeName,
fontSize: clideFontSmall,
fontFamily: ClideSettings.fonts.monoOf(context),
color: hovered ? tokens.globalForeground : tokens.globalFocus,
),
],
),
);
@@ -518,7 +523,7 @@ class _OpenProjectDialogState extends State<_OpenProjectDialog> {
style: TextStyle(
color: tokens.globalForeground,
fontSize: clideFontCaption,
fontFamily: clideMonoFamily,
fontFamily: ClideSettings.fonts.monoOf(context),
fontFamilyFallback: clideMonoFamilyFallback,
),
cursorColor: tokens.globalForeground,