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:
@@ -36,8 +36,15 @@ These apply across every reference and every surface:
|
||||
- Never use `Material*` or `Cupertino*` widgets or color constants — clide
|
||||
is `WidgetsApp` only (D-7).
|
||||
- Use `ClideText` for themed text; never bare `Text` in production widgets.
|
||||
- Typography: `clideFontMono` for code/paths/IDs, `clideFontCaption` for
|
||||
- Typography sizes: `clideFontMono` for code/paths/IDs, `clideFontCaption` for
|
||||
status/section headers, body inherits from `DefaultTextStyle`.
|
||||
- Font *family* comes from the user-selectable facade, not a const: a
|
||||
monospace surface uses `fontFamily: ClideSettings.fonts.monoOf(context)`
|
||||
(and `fontFamilyFallback: clideMonoFamilyFallback`); the UI face is inherited
|
||||
via the root `DefaultTextStyle`, or `ClideSettings.fonts.uiOf(context)` when a
|
||||
widget must set it explicitly. `clideMonoFamily` / `clideUiFamily` are the
|
||||
facade's defaults — don't read them directly in new widgets (D-101). Same
|
||||
facade exposes `ClideSettings.theme.of(context)` and `.i18n.of(context)`.
|
||||
|
||||
## Conversation-panel cards (T-305)
|
||||
|
||||
|
||||
@@ -297,3 +297,4 @@ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDA10DRQE3SED10JAW2CZDCR', 'T-469', '2026-06-17 09:57:53.221', '2026-06-17 09:57:53.221', NULL, '2a72e3e647afc1b5b235ab1a97d298f8', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDB1VFADV4QK1YG29T658T8C', 'T-470', '2026-06-17 12:21:23.411', '2026-06-17 12:21:23.411', NULL, '0f95e5a1faa7a7e902c765f449cceb15', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDJ17C3GZWNE98NRVXP189C', 'T-471', '2026-06-17 18:11:42.049', '2026-06-17 18:11:42.049', NULL, '10a9a957d647a63ce69dc4b6b564f6aa', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDTCGG5Z0KNQKF89W1VZSQ8', 'T-472', '2026-06-17 18:48:11.649', '2026-06-17 18:48:11.649', NULL, '097120cff851cb2dac9f54937f4c7b17', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
|
||||
@@ -7237,3 +7237,5 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat
|
||||
**Files:** `pubspec.yaml` (fonts:), `assets/fonts/inter/`, `assets/licenses.yaml`, `lib/widgets/src/typography.dart`, `lib/src/shell/root_shell.dart` (DefaultTextStyle), mono consumers (editor / terminal / markdown / code-block), the Appearance settings category (T-452) + schema registration.
|
||||
|
||||
**Related:** T-444 (epic), T-452 (Appearance category — where these render), T-448 (field renderer), T-449 (scope), ui-design `theme.md` (typography section: clideUiFamily / clideMonoFamily / clideFont* sizes), D-42 (bundled-dep documentation), D-44 (theming).', 'done', 'medium', NULL, NULL, NULL, '2026-06-17 07:39:25.019', '2026-06-17 18:14:38.272', NULL, '138f95c597eebc2db37c9e0c4bc49c91', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDJ17C3GZWNE98NRVXP189C', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Settings → Appearance: monospace font picker (needs context-driven mono family)', 'Follow-up to T-460. T-460 shipped the UI-font picker (live via the root DefaultTextStyle, single seam) + bundled Inter as default. The MONOSPACE picker is deferred because clideMonoFamily is referenced as a const in ~38 sites (terminal, diff, code blocks, ClideText fontFamily) — not inherited. To make the mono picker apply live, route the mono family through context (a small FontScope InheritedWidget or the theme) read by those sites, then add a ''Monospace font'' select in Appearance writing app.mono.font (kMonoFontSettingKey already defined in typography.dart). Default JetBrains Mono.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-17 18:11:42.049', '2026-06-17 18:18:13.607', NULL, 'c401f1c31a8bd9f8cb252f718696d07e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDTCGG5Z0KNQKF89W1VZSQ8', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Migrate 11 context-less mono-font sites onto ClideSettings.fonts (thread family through helpers)', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-17 18:48:11.649', '2026-06-17 18:48:11.649', NULL, '267cb357e1a5532db0a4bb0f9312ccf2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
|
||||
@@ -142,6 +142,7 @@ You might also want, project-permitting:
|
||||
- [D-98: Remote-tool contract + connect preflight](decisions/architecture.md#d-98-remote-tool-contract--connect-preflight) — _architecture_
|
||||
- [D-99: Remote session identity keyed on (host, workspace)](decisions/architecture.md#d-99-remote-session-identity-keyed-on-host-workspace) — _architecture_
|
||||
- [D-100: Fence `dart:ffi` behind conditional imports + web stubs to keep the web/WASM target compiling](decisions/tooling.md#d-100-fence-dartffi-behind-conditional-imports--web-stubs-to-keep-the-webwasm-target-compiling) — _tooling_
|
||||
- [D-101: ClideSettings — one live-preferences access facade](decisions/architecture.md#d-101-clidesettings--one-live-preferences-access-facade) — _architecture_
|
||||
|
||||
## Open questions
|
||||
|
||||
|
||||
@@ -534,4 +534,12 @@ Core, rendering, IPC, kernel, panel manager.
|
||||
- **Cross-reference:** [D-41](#d-41-claude-panes-one-primary-per-repo-tmux-backed), [D-70](#d-70-ipc-socket-path-is-per-workspace-deterministic), [D-77](#d-77-drive-claude-via-the-stream-json-control-protocol-teams-become-a-clide-owned-coordination-layer), [D-93](#d-93-clide-writes-no-directories-of-its-own-into-the-workspace), [D-96](#d-96-remote-execution-footprint--no-install-ssh-exec), [D-97](#d-97-ssh-workspace-uri--system-ssh-auth). Implemented across T-332 (identity carrier) and T-333 (session re-key).
|
||||
- **Raised by:** 2026-06-12 — T-330 spike artifacts ("session identity keyed on (host, repo) amending D-41/D-77").
|
||||
|
||||
### D-101: ClideSettings — one live-preferences access facade
|
||||
- **Date:** 2026-06-17
|
||||
- **Decision:** Live, user-selectable preferences are read through a single widget-facing facade, `ClideSettings`, namespaced by concern: `ClideSettings.fonts.monoOf(context)` / `.fonts.uiOf(context)`, `ClideSettings.theme.of(context)`, `ClideSettings.i18n.of(context)`. Values originate in the kernel `SettingsStore`; the app root (`root_shell`) resolves them and provides a `ClideSettingsScope` InheritedWidget (carrying the font families), rebuilding it on a settings change so dependents re-read live. Theme and i18n **delegate** to their existing live providers (`ClideTheme` / the `I18n` service) rather than being duplicated — one source of truth. Reads outside a scope fall back to the bundled font defaults, so a widget renders without a provider (isolated tests). "Plumb once, use many."
|
||||
- **Rationale:** Before this, each live setting had its own ad-hoc read path — theme via `ClideTheme.of`, i18n via `ClideKernel.of(context).i18n`, fonts as a compile-time `const` that couldn't change at runtime at all. A new preference meant inventing another path. One facade gives every current and future preference a uniform, discoverable read site and a single root resolution point — without a mutable global (the shortcut rejected during the T-471 font-flow design) and without a big-bang rewrite of the established theme/i18n providers (they delegate, so their consumers migrate incrementally).
|
||||
- **Cost:** Two read paths coexist during migration — `ClideTheme.of` / `i18n.string` still work (the facade delegates to them), so their hundreds of call sites move to `ClideSettings.theme` / `.i18n` incrementally rather than at once. The font consts (`clideMonoFamily` / `clideUiFamily`) remain as the facade's defaults; ~11 context-less helper sites still read the const directly, migrated in a follow-up (T-472). The facade lives in `widgets` and reaches into `kernel` for the theme/i18n delegates (a dependency already present).
|
||||
- **Cross-reference:** Fonts landed it: T-460 (Inter default + UI picker) and T-471 (mono picker) migrated ~93 sites onto `ClideSettings.fonts`. Consumer migration of theme + i18n onto the facade, and the context-less font stragglers (T-472), are staged follow-ups. Values live in the kernel `SettingsStore`.
|
||||
- **Raised by:** 2026-06-17 — user, during T-471 font-flow design: "I do see reason in nesting them all in one settings object that dynamically loads so we can extend it in the future… plumb once, use many."
|
||||
|
||||
---
|
||||
|
||||
@@ -37,10 +37,13 @@ class ClaudeBanner extends StatelessWidget {
|
||||
const SizedBox(height: 18),
|
||||
ClideText('Claude', fontSize: clideFontDialogTitle, color: claudeAccent, fontWeight: FontWeight.w500),
|
||||
const SizedBox(height: 2),
|
||||
ClideText(role, fontSize: clideFontSmall, muted: true, fontFamily: clideMonoFamily),
|
||||
ClideText(role, fontSize: clideFontSmall, muted: true, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const SizedBox(height: 16),
|
||||
if (ws != null) ClideText(ws, fontSize: clideFontCaption, muted: true),
|
||||
if (statusLine != null) ...[const SizedBox(height: 2), ClideText(statusLine!, fontSize: clideFontSmall, muted: true, fontFamily: clideMonoFamily)],
|
||||
if (statusLine != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
ClideText(statusLine!, fontSize: clideFontSmall, muted: true, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
const ClideText('Warming up — your conversation will appear here.', fontSize: clideFontSmall, muted: true),
|
||||
],
|
||||
|
||||
@@ -132,12 +132,13 @@ class _ClaudePaneState extends State<ClaudePane> {
|
||||
final seg = statusSegmentsAroundMode(_status);
|
||||
final mode = _status.permissionMode;
|
||||
|
||||
Widget text(String t) => ClideText(t, fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: tokens.statusBarForeground, maxLines: 1);
|
||||
Widget text(String t) =>
|
||||
ClideText(t, fontSize: clideFontSmall, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.statusBarForeground, maxLines: 1);
|
||||
|
||||
final children = <Widget>[];
|
||||
void add(Widget w) {
|
||||
if (children.isNotEmpty) {
|
||||
children.add(ClideText(' · ', fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: tokens.globalTextMuted, maxLines: 1));
|
||||
children.add(ClideText(' · ', fontSize: clideFontSmall, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.globalTextMuted, maxLines: 1));
|
||||
}
|
||||
children.add(w);
|
||||
}
|
||||
@@ -857,7 +858,13 @@ class _ModeBadge extends StatelessWidget {
|
||||
return Semantics(
|
||||
label: 'permission mode: ${permissionModeLabel(mode)}',
|
||||
excludeSemantics: true,
|
||||
child: ClideText(permissionModeLabel(mode), fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: permissionModeColor(mode, tokens), maxLines: 1),
|
||||
child: ClideText(
|
||||
permissionModeLabel(mode),
|
||||
fontSize: clideFontSmall,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
color: permissionModeColor(mode, tokens),
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,13 +241,13 @@ class _ConversationCardState extends State<ConversationCard> {
|
||||
if (widget.collapsible) _caret(tokens),
|
||||
// Header label size matches ClideCollapserCard (clideFontCaption) so
|
||||
// neighbouring cards in the conversation stream align (T-344).
|
||||
ClideText(widget.label, fontSize: clideFontCaption, color: widget.accent, fontFamily: clideMonoFamily),
|
||||
ClideText(widget.label, fontSize: clideFontCaption, color: widget.accent, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
// While collapsed, show a one-line gist next to the label so the card
|
||||
// still says what it holds.
|
||||
if (_collapsed && summary != null) ...[
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: ClideText(summary, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily, maxLines: 1),
|
||||
child: ClideText(summary, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context), maxLines: 1),
|
||||
),
|
||||
] else
|
||||
const Spacer(),
|
||||
@@ -322,7 +322,7 @@ class _ConversationCardState extends State<ConversationCard> {
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
ClideText(label, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
ClideText(label, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: Container(height: 1, color: tokens.panelBorder)),
|
||||
],
|
||||
@@ -349,7 +349,7 @@ class _ConversationCardState extends State<ConversationCard> {
|
||||
onTap: items[i].onTap,
|
||||
builder: (_, hovered, pressed) => Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: ClideText(items[i].label, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
child: ClideText(items[i].label, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -147,9 +147,14 @@ class _ModelPickerCardState extends State<ModelPickerCard> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ClideText(widget.title, fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: tokens.statusInfo),
|
||||
ClideText(widget.title, fontSize: clideFontSmall, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.statusInfo),
|
||||
const Spacer(),
|
||||
ClideText('↑↓ · 1-${widget.models.length} · Enter · Esc', fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalTextMuted),
|
||||
ClideText(
|
||||
'↑↓ · 1-${widget.models.length} · Enter · Esc',
|
||||
fontSize: clideFontMeta,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
color: tokens.globalTextMuted,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
@@ -122,7 +122,7 @@ class _ToolPromptCardState extends State<ToolPromptCard> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ClideText(label, fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: accent),
|
||||
ClideText(label, fontSize: clideFontSmall, fontFamily: ClideSettings.fonts.monoOf(context), color: accent),
|
||||
const SizedBox(height: 8),
|
||||
...children,
|
||||
],
|
||||
@@ -362,14 +362,21 @@ class _ToolPromptCardState extends State<ToolPromptCard> {
|
||||
border: Border.all(color: tokens.statusInfo),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: ClideText(text, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalForeground),
|
||||
child: ClideText(text, fontSize: clideFontMeta, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.globalForeground),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
chips.add(ClideText(text, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: done ? tokens.statusSuccess : tokens.globalTextMuted));
|
||||
chips.add(
|
||||
ClideText(
|
||||
text,
|
||||
fontSize: clideFontMeta,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
color: done ? tokens.statusSuccess : tokens.globalTextMuted,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
chips.add(ClideText('Review', fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalTextMuted));
|
||||
chips.add(ClideText('Review', fontSize: clideFontMeta, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.globalTextMuted));
|
||||
chips.add(ClideText('›', color: tokens.globalTextMuted, fontSize: 15));
|
||||
return Wrap(spacing: 10, runSpacing: 6, crossAxisAlignment: WrapCrossAlignment.center, children: chips);
|
||||
}
|
||||
@@ -399,7 +406,8 @@ class _ToolPromptCardState extends State<ToolPromptCard> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (q.header.isNotEmpty) ClideText(q.header.toUpperCase(), fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalTextMuted),
|
||||
if (q.header.isNotEmpty)
|
||||
ClideText(q.header.toUpperCase(), fontSize: clideFontMeta, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.globalTextMuted),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2, bottom: 6),
|
||||
child: ClideText(q.question, color: tokens.globalForeground),
|
||||
|
||||
@@ -178,7 +178,7 @@ class _TeammateTile extends StatelessWidget {
|
||||
children: [
|
||||
Container(width: 3, height: 13, color: accent),
|
||||
const SizedBox(width: 6),
|
||||
ClideText(member.name, fontSize: clideFontSmall, color: accent, fontFamily: clideMonoFamily),
|
||||
ClideText(member.name, fontSize: clideFontSmall, color: accent, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ClideText(
|
||||
|
||||
@@ -148,19 +148,22 @@ class _DecisionDetailViewState extends State<DecisionDetailView> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ClideText(id, fontSize: clideFontSmall, color: typeColor, fontFamily: clideMonoFamily),
|
||||
ClideText(id, fontSize: clideFontSmall, color: typeColor, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const Spacer(),
|
||||
if (domain != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(color: tokens.panelBorder, borderRadius: BorderRadius.circular(3)),
|
||||
child: ClideText(domain, fontSize: clideFontBadge, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
child: ClideText(domain, fontSize: clideFontBadge, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ClideText(title, fontSize: 15, fontWeight: FontWeight.w500),
|
||||
if (date != null) ...[const SizedBox(height: 6), ClideText(date, muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily)],
|
||||
if (date != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
ClideText(date, muted: true, fontSize: clideFontSmall, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
],
|
||||
if (status != null && status != 'active') ...[const SizedBox(height: 8), _StatusBadge(status: status, tokens: tokens)],
|
||||
],
|
||||
),
|
||||
@@ -168,7 +171,7 @@ class _DecisionDetailViewState extends State<DecisionDetailView> {
|
||||
if (body != null && body.isNotEmpty) ...[const SizedBox(height: 12), ClideMarkdown(body, onRecordTap: (id) => _navigateToRecord(context, id))],
|
||||
if (refs.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
ClideText('CROSS-REFERENCES', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
|
||||
ClideText('CROSS-REFERENCES', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const SizedBox(height: 6),
|
||||
for (final ref in refs) _RefCard(ref: ref, tokens: tokens),
|
||||
],
|
||||
@@ -201,7 +204,7 @@ class _RefCard extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
ClideText(targetId, fontSize: clideFontSmall, color: tokens.globalFocus, fontFamily: clideMonoFamily),
|
||||
ClideText(targetId, fontSize: clideFontSmall, color: tokens.globalFocus, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const SizedBox(width: 8),
|
||||
ClideText(refType, fontSize: clideFontSmall, color: tokens.globalTextMuted),
|
||||
],
|
||||
@@ -227,7 +230,7 @@ class _StatusBadge extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(color: color.withAlpha(0x30), borderRadius: BorderRadius.circular(3)),
|
||||
child: ClideText(status.toUpperCase(), fontSize: clideFontBadge, color: color, fontFamily: clideMonoFamily),
|
||||
child: ClideText(status.toUpperCase(), fontSize: clideFontBadge, color: color, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,9 +309,10 @@ class _DecisionCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
ClideText(entry.id, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
ClideText(entry.id, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const Spacer(),
|
||||
if (entry.domain != null) ClideText(entry.domain!, fontSize: clideFontBadge, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
if (entry.domain != null)
|
||||
ClideText(entry.domain!, fontSize: clideFontBadge, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -321,7 +322,7 @@ class _DecisionCard extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(color: tokens.statusSuccess.withAlpha(0x30), borderRadius: BorderRadius.circular(3)),
|
||||
child: ClideText('resolved', fontSize: clideFontBadge, color: tokens.statusSuccess, fontFamily: clideMonoFamily),
|
||||
child: ClideText('resolved', fontSize: clideFontBadge, color: tokens.statusSuccess, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -83,7 +83,7 @@ class _DeepLinkConfirmDialog extends StatelessWidget {
|
||||
const SizedBox(height: 6),
|
||||
ClideText('A clide:// link from outside the app is asking to:', muted: true, fontSize: clideFontSmall),
|
||||
const SizedBox(height: 8),
|
||||
ClideText(action.describe, fontFamily: clideMonoFamily, fontSize: clideFontSmall, color: t.globalForeground),
|
||||
ClideText(action.describe, fontFamily: ClideSettings.fonts.monoOf(context), fontSize: clideFontSmall, color: t.globalForeground),
|
||||
const SizedBox(height: 8),
|
||||
ClideText('Only allow this if you trust where the link came from.', fontSize: clideFontMeta, color: t.statusWarning),
|
||||
const SizedBox(height: 14),
|
||||
|
||||
@@ -250,7 +250,7 @@ class _HunkView extends StatelessWidget {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
||||
child: ClideText(header, fontSize: clideFontMono, muted: true, fontFamily: clideMonoFamily),
|
||||
child: ClideText(header, fontSize: clideFontMono, muted: true, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
),
|
||||
for (final lineObj in lines) _DiffLineRow(line: (lineObj as Map).cast<String, Object?>()),
|
||||
],
|
||||
@@ -294,7 +294,7 @@ class _DiffLineRow extends StatelessWidget {
|
||||
oldLineNo != null ? '${oldLineNo.toInt()}' : '',
|
||||
fontSize: clideFontMono,
|
||||
muted: true,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
@@ -305,15 +305,22 @@ class _DiffLineRow extends StatelessWidget {
|
||||
newLineNo != null ? '${newLineNo.toInt()}' : '',
|
||||
fontSize: clideFontMono,
|
||||
muted: true,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
ClideText(prefix, fontSize: clideFontMono, color: fg, fontFamily: clideMonoFamily),
|
||||
ClideText(prefix, fontSize: clideFontMono, color: fg, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const SizedBox(width: 2),
|
||||
Expanded(
|
||||
child: ClideText(text, fontSize: clideFontMono, color: fg, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.clip),
|
||||
child: ClideText(
|
||||
text,
|
||||
fontSize: clideFontMono,
|
||||
color: fg,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -371,7 +371,12 @@ class _TextBody extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final style = TextStyle(color: foreground, fontSize: clideFontMono, fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback);
|
||||
final style = TextStyle(
|
||||
color: foreground,
|
||||
fontSize: clideFontMono,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
fontFamilyFallback: clideMonoFamilyFallback,
|
||||
);
|
||||
final editable = EditableText(
|
||||
controller: controller,
|
||||
focusNode: focus,
|
||||
|
||||
@@ -48,7 +48,7 @@ class ExtensionsNotice extends StatelessWidget {
|
||||
i.string('notice.tracked', namespace: ns, placeholder: 'Tracked in T-8 (Tier 6) · D-16'),
|
||||
color: tokens.globalTextMuted,
|
||||
fontSize: clideFontCaption,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -164,7 +164,12 @@ class _BranchPickerState extends State<_BranchPicker> {
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ClideText('Switch branch', fontSize: clideFontCaption, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
child: ClideText(
|
||||
'Switch branch',
|
||||
fontSize: clideFontCaption,
|
||||
color: tokens.globalTextMuted,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => widget.onDismiss(),
|
||||
@@ -229,7 +234,7 @@ class _BranchRow extends StatelessWidget {
|
||||
Expanded(
|
||||
child: ClideText(
|
||||
name,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
fontSize: clideFontMono,
|
||||
color: current ? tokens.globalForeground : tokens.listItemForeground,
|
||||
),
|
||||
|
||||
@@ -67,7 +67,7 @@ class _Kv extends StatelessWidget {
|
||||
children: [
|
||||
SizedBox(width: 90, child: ClideText(label, fontSize: 13, color: tokens.globalTextMuted)),
|
||||
Expanded(
|
||||
child: ClideText(value, fontSize: 13, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
child: ClideText(value, fontSize: 13, fontFamily: ClideSettings.fonts.monoOf(context), maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -106,7 +106,7 @@ class _Licenses extends StatelessWidget {
|
||||
child: ClideText(
|
||||
'${d.name} ${d.version} · ${d.license}',
|
||||
fontSize: 12,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
color: tokens.globalTextMuted,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
||||
@@ -137,7 +137,12 @@ class _OpenFolderDialogState extends State<OpenFolderDialog> {
|
||||
child: EditableText(
|
||||
controller: _controller,
|
||||
focusNode: _focus,
|
||||
style: TextStyle(color: tokens.globalForeground, fontSize: 14, fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback),
|
||||
style: TextStyle(
|
||||
color: tokens.globalForeground,
|
||||
fontSize: 14,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
fontFamilyFallback: clideMonoFamilyFallback,
|
||||
),
|
||||
cursorColor: tokens.globalForeground,
|
||||
backgroundCursorColor: tokens.globalTextMuted,
|
||||
onSubmitted: (_) => unawaited(_submit()),
|
||||
|
||||
@@ -162,7 +162,12 @@ class _TopMenuButtonState extends State<_TopMenuButton> {
|
||||
label: node.title,
|
||||
enabled: node.enabled,
|
||||
trailing: node.keybinding != null
|
||||
? ClideText(node.keybinding!, fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: ClideTheme.of(ctx).surface.globalTextMuted)
|
||||
? ClideText(
|
||||
node.keybinding!,
|
||||
fontSize: clideFontSmall,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
color: ClideTheme.of(ctx).surface.globalTextMuted,
|
||||
)
|
||||
: null,
|
||||
onSelect: () => unawaited(widget.kernel.commands.execute(node.commandId)),
|
||||
)
|
||||
|
||||
@@ -217,11 +217,11 @@ class _LogRow extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClideText('$hh:$mm:$ss', fontSize: clideFontMono, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
ClideText('$hh:$mm:$ss', fontSize: clideFontMono, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: 48,
|
||||
child: ClideText(record.level.name.toUpperCase(), fontSize: clideFontMono, color: fg, fontFamily: clideMonoFamily),
|
||||
child: ClideText(record.level.name.toUpperCase(), fontSize: clideFontMono, color: fg, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
@@ -230,14 +230,14 @@ class _LogRow extends StatelessWidget {
|
||||
record.source,
|
||||
fontSize: clideFontMono,
|
||||
color: tokens.globalTextMuted,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ClideText(record.message, fontSize: clideFontMono, color: fg, fontFamily: clideMonoFamily),
|
||||
child: ClideText(record.message, fontSize: clideFontMono, color: fg, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -228,7 +228,7 @@ class _ScoreBar extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
ClideText('${(score * 100).round()}%', fontSize: clideFontBadge, muted: true, fontFamily: clideMonoFamily),
|
||||
ClideText('${(score * 100).round()}%', fontSize: clideFontBadge, muted: true, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ class _ProblemRow extends StatelessWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ClideText(problem.source, fontSize: clideFontMono, color: tokens.statusWarning, fontFamily: clideMonoFamily),
|
||||
ClideText(problem.source, fontSize: clideFontMono, color: tokens.statusWarning, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(child: ClideText(problem.message, color: tokens.sidebarForeground, maxLines: 2)),
|
||||
],
|
||||
@@ -121,7 +121,7 @@ class _ProblemRow extends StatelessWidget {
|
||||
if (problem.hint != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 44, top: 2),
|
||||
child: ClideText(problem.hint!, fontSize: clideFontMono, muted: true, fontFamily: clideMonoFamily),
|
||||
child: ClideText(problem.hint!, fontSize: clideFontMono, muted: true, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -255,7 +255,7 @@ class _Toggle extends StatelessWidget {
|
||||
),
|
||||
child: ClideText(
|
||||
label,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
fontSize: clideFontCaption,
|
||||
color: active ? tokens.listItemSelectedForeground : tokens.sidebarForeground,
|
||||
),
|
||||
@@ -344,7 +344,7 @@ class _MatchRow extends StatelessWidget {
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 36,
|
||||
child: ClideText('${match.line}', fontSize: clideFontCaption, fontFamily: clideMonoFamily, color: tokens.globalTextMuted),
|
||||
child: ClideText('${match.line}', fontSize: clideFontCaption, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.globalTextMuted),
|
||||
),
|
||||
Expanded(child: replacement.isEmpty ? _highlighted() : _preview()),
|
||||
],
|
||||
|
||||
@@ -128,7 +128,12 @@ class _SectionCard extends StatelessWidget {
|
||||
if (section.label.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 2, bottom: 6),
|
||||
child: ClideText(section.label.toUpperCase(), fontSize: clideFontCaption, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
|
||||
child: ClideText(
|
||||
section.label.toUpperCase(),
|
||||
fontSize: clideFontCaption,
|
||||
color: tokens.sidebarSectionHeader,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
),
|
||||
),
|
||||
ClideSurface(
|
||||
// Card surface (surface.md): panelHeader resolves to the `surface`
|
||||
@@ -440,7 +445,7 @@ class _EditControlState extends State<_EditControl> {
|
||||
child: EditableText(
|
||||
controller: _controller,
|
||||
focusNode: _focus,
|
||||
style: TextStyle(fontFamily: clideMonoFamily, fontSize: clideFontMono, color: tokens.globalForeground),
|
||||
style: TextStyle(fontFamily: ClideSettings.fonts.monoOf(context), fontSize: clideFontMono, color: tokens.globalForeground),
|
||||
cursorColor: tokens.globalFocus,
|
||||
backgroundCursorColor: tokens.globalTextMuted,
|
||||
maxLines: 1,
|
||||
|
||||
@@ -202,7 +202,7 @@ class _CategoryRail extends StatelessWidget {
|
||||
i.string('rail.header', namespace: SettingsModal.ns, placeholder: 'Categories'),
|
||||
fontSize: clideFontCaption,
|
||||
color: tokens.sidebarSectionHeader,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
||||
@@ -75,7 +75,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
i.string('section.appearance', namespace: SettingsView.ns, placeholder: 'Appearance'),
|
||||
fontSize: clideFontCaption,
|
||||
color: tokens.sidebarSectionHeader,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_HighContrastToggle(
|
||||
|
||||
@@ -148,16 +148,17 @@ class _TicketHeader extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ClideText(detail.id, fontSize: clideFontSmall, color: typeColor, fontFamily: clideMonoFamily),
|
||||
ClideText(detail.id, fontSize: clideFontSmall, color: typeColor, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const Spacer(),
|
||||
if (detail.priority != null) ClideText(detail.priority!, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
if (detail.priority != null)
|
||||
ClideText(detail.priority!, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ClideText(detail.title, fontSize: 15, fontWeight: FontWeight.w500),
|
||||
if (detail.assignedTo != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
ClideText('assigned: ${detail.assignedTo}', muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily),
|
||||
ClideText('assigned: ${detail.assignedTo}', muted: true, fontSize: clideFontSmall, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
],
|
||||
],
|
||||
),
|
||||
@@ -206,7 +207,7 @@ class _StatusControls extends StatelessWidget {
|
||||
border: Border.all(color: active ? tokens.statusInfo : tokens.panelBorder),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: ClideText(_shortLabel(s), fontSize: clideFontBadge, color: color, fontFamily: clideMonoFamily),
|
||||
child: ClideText(_shortLabel(s), fontSize: clideFontBadge, color: color, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -234,7 +235,7 @@ class _SectionLabel extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClideText(label, fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily);
|
||||
return ClideText(label, fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: ClideSettings.fonts.monoOf(context));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +271,7 @@ class _CompactCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
ClideText(id, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
ClideText(id, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: ClideText(title, fontSize: clideFontSmall)),
|
||||
],
|
||||
@@ -320,9 +321,10 @@ class _DecisionRefCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
ClideText(id, fontSize: clideFontSmall, color: color, fontFamily: clideMonoFamily),
|
||||
ClideText(id, fontSize: clideFontSmall, color: color, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
const Spacer(),
|
||||
if (domain != null) ClideText(domain, fontSize: clideFontBadge, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
if (domain != null)
|
||||
ClideText(domain, fontSize: clideFontBadge, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
|
||||
@@ -314,7 +314,12 @@ class _TypeChip extends StatelessWidget {
|
||||
decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
ClideText(label, fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: active ? tokens.globalForeground : tokens.globalTextMuted),
|
||||
ClideText(
|
||||
label,
|
||||
fontSize: clideFontSmall,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
color: active ? tokens.globalForeground : tokens.globalTextMuted,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -386,7 +391,7 @@ class _TicketCard extends StatelessWidget {
|
||||
entry.parentId!,
|
||||
fontSize: clideFontSmall,
|
||||
color: hovered ? tokens.globalForeground : tokens.globalTextMuted,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -402,7 +407,13 @@ class _TicketCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
ClideText(entry.id, fontSize: clideFontSmall, color: tokens.globalForeground, fontFamily: clideMonoFamily, fontWeight: FontWeight.w600),
|
||||
ClideText(
|
||||
entry.id,
|
||||
fontSize: clideFontSmall,
|
||||
color: tokens.globalForeground,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -480,7 +491,7 @@ class _StatusBadge extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(color: color.withAlpha(0x30), borderRadius: BorderRadius.circular(3)),
|
||||
child: ClideText(label, fontSize: clideFontBadge, color: color, fontFamily: clideMonoFamily),
|
||||
child: ClideText(label, fontSize: clideFontBadge, color: color, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,12 @@ class VimModeIndicator extends StatelessWidget {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: ClideText('-- ${service.mode.label} --', fontFamily: clideMonoFamily, fontSize: clideFontCaption, color: tokens.statusBarForeground),
|
||||
child: ClideText(
|
||||
'-- ${service.mode.label} --',
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
fontSize: clideFontCaption,
|
||||
color: tokens.statusBarForeground,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -34,7 +34,12 @@ class ProjectSwitcherButton extends StatelessWidget {
|
||||
builder: (context, hovered, _) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ClideText(label, fontSize: 12, color: hovered ? tokens.globalForeground : tokens.chromeForeground, fontFamily: clideMonoFamily),
|
||||
ClideText(
|
||||
label,
|
||||
fontSize: 12,
|
||||
color: hovered ? tokens.globalForeground : tokens.chromeForeground,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
ClideIcon(PhosphorIcons.byName('caret-down'), size: 8, color: tokens.chromeForeground),
|
||||
],
|
||||
@@ -195,7 +200,7 @@ class _RecentProjectRow extends StatelessWidget {
|
||||
project.relativePath,
|
||||
muted: true,
|
||||
fontSize: 12,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -203,11 +208,18 @@ class _RecentProjectRow extends StatelessWidget {
|
||||
ClideText(' · ', muted: true, fontSize: 12),
|
||||
ClideIcon(PhosphorIcons.byName('git-branch'), size: 10, color: tokens.globalTextMuted),
|
||||
const SizedBox(width: 3),
|
||||
ClideText(project.branch!, muted: true, fontSize: 12, fontFamily: clideMonoFamily),
|
||||
ClideText(project.branch!, muted: true, fontSize: 12, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
],
|
||||
)
|
||||
else
|
||||
ClideText(project.relativePath, muted: true, fontSize: 12, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
ClideText(
|
||||
project.relativePath,
|
||||
muted: true,
|
||||
fontSize: 12,
|
||||
fontFamily: ClideSettings.fonts.monoOf(context),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -236,7 +248,8 @@ class _ActionRow extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: ClideText(label, fontSize: 14)),
|
||||
if (shortcut != null && shortcut!.isNotEmpty) ClideText(shortcut!, fontSize: 12, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
if (shortcut != null && shortcut!.isNotEmpty)
|
||||
ClideText(shortcut!, fontSize: 12, color: tokens.globalTextMuted, fontFamily: ClideSettings.fonts.monoOf(context)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
+119
-110
@@ -74,118 +74,127 @@ class RootShellState extends State<RootShell> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
return DefaultTextStyle(
|
||||
style: TextStyle(
|
||||
color: tokens.globalForeground,
|
||||
fontSize: 15,
|
||||
height: clideLineHeight,
|
||||
fontWeight: clideUiDefaultWeight,
|
||||
// User-selected UI font (Settings → Appearance, T-460), else the
|
||||
// bundled default. The fallback chain still covers a missing asset.
|
||||
fontFamily: widget.services.settings.get<String>(kUiFontSettingKey) ?? clideUiFamily,
|
||||
fontFamilyFallback: clideUiFamilyFallback,
|
||||
),
|
||||
child: MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaler: TextScaler.linear(widget.services.textZoom.scale)),
|
||||
child: Actions(
|
||||
actions: <Type, Action<Intent>>{
|
||||
TextScaleIncreaseIntent: CallbackAction<TextScaleIncreaseIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.textZoom.increase();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextScaleDecreaseIntent: CallbackAction<TextScaleDecreaseIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.textZoom.decrease();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextScaleResetIntent: CallbackAction<TextScaleResetIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.textZoom.reset();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
InvokeCommandIntent: CallbackAction<InvokeCommandIntent>(
|
||||
onInvoke: (intent) {
|
||||
widget.services.commands.execute(intent.commandId);
|
||||
return null;
|
||||
},
|
||||
),
|
||||
PaletteOpenIntent: CallbackAction<PaletteOpenIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.palette.open();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
QuickOpenIntent: CallbackAction<QuickOpenIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.quickOpen.open();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
ExLineOpenIntent: CallbackAction<ExLineOpenIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.exLine.open();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
ExLineWriteQuitIntent: CallbackAction<ExLineWriteQuitIntent>(
|
||||
onInvoke: (_) {
|
||||
// ZZ — save+close the active tab without opening the overlay.
|
||||
unawaited(exWriteQuitActive(widget.services.ipc));
|
||||
return null;
|
||||
},
|
||||
),
|
||||
FindInFilesIntent: CallbackAction<FindInFilesIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.arrangement.setVisible(Slots.sidebar, true);
|
||||
widget.services.arrangement.setCollapsed(Slots.sidebar, false);
|
||||
widget.services.panels.activateTab(Slots.sidebar, 'search.findInFiles');
|
||||
return null;
|
||||
},
|
||||
),
|
||||
FocusNextPanelIntent: CallbackAction<FocusNextPanelIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.focus.focusNextSlot();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
FocusPreviousPanelIntent: CallbackAction<FocusPreviousPanelIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.focus.focusPreviousSlot();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
},
|
||||
child: KeyboardListener(
|
||||
focusNode: _keyFocus,
|
||||
autofocus: true,
|
||||
onKeyEvent: _onKey,
|
||||
child: ColoredBox(
|
||||
color: tokens.globalBackground,
|
||||
child: ClideResizeBorder(
|
||||
windowControls: widget.services.window,
|
||||
child: Column(
|
||||
children: [
|
||||
HatBar(kernel: widget.services, menuBar: _menuBar),
|
||||
Expanded(
|
||||
child: DialogHost(
|
||||
router: widget.services.dialog,
|
||||
child: Stack(
|
||||
children: [
|
||||
const Positioned.fill(child: RootLayout()),
|
||||
const ClidePalette(),
|
||||
const QuickOpenOverlay(),
|
||||
const ExLineOverlay(),
|
||||
const Positioned.fill(child: _WelcomeOverlay()),
|
||||
const ToastOverlay(),
|
||||
],
|
||||
// Resolve the user-selected fonts once (Settings → Appearance, T-460/T-471)
|
||||
// and publish them via ClideFonts; the settings listener rebuilds this on a
|
||||
// change so descendants re-read live. The UI font flows through the
|
||||
// DefaultTextStyle below; mono sites read ClideFonts.monoOf(context).
|
||||
final settings = widget.services.settings;
|
||||
final uiFont = settings.get<String>(kUiFontSettingKey) ?? clideUiFamily;
|
||||
final monoFont = settings.get<String>(kMonoFontSettingKey) ?? clideMonoFamily;
|
||||
return ClideSettingsScope(
|
||||
ui: uiFont,
|
||||
mono: monoFont,
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(
|
||||
color: tokens.globalForeground,
|
||||
fontSize: 15,
|
||||
height: clideLineHeight,
|
||||
fontWeight: clideUiDefaultWeight,
|
||||
fontFamily: uiFont,
|
||||
fontFamilyFallback: clideUiFamilyFallback,
|
||||
),
|
||||
child: MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaler: TextScaler.linear(widget.services.textZoom.scale)),
|
||||
child: Actions(
|
||||
actions: <Type, Action<Intent>>{
|
||||
TextScaleIncreaseIntent: CallbackAction<TextScaleIncreaseIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.textZoom.increase();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextScaleDecreaseIntent: CallbackAction<TextScaleDecreaseIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.textZoom.decrease();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextScaleResetIntent: CallbackAction<TextScaleResetIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.textZoom.reset();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
InvokeCommandIntent: CallbackAction<InvokeCommandIntent>(
|
||||
onInvoke: (intent) {
|
||||
widget.services.commands.execute(intent.commandId);
|
||||
return null;
|
||||
},
|
||||
),
|
||||
PaletteOpenIntent: CallbackAction<PaletteOpenIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.palette.open();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
QuickOpenIntent: CallbackAction<QuickOpenIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.quickOpen.open();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
ExLineOpenIntent: CallbackAction<ExLineOpenIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.exLine.open();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
ExLineWriteQuitIntent: CallbackAction<ExLineWriteQuitIntent>(
|
||||
onInvoke: (_) {
|
||||
// ZZ — save+close the active tab without opening the overlay.
|
||||
unawaited(exWriteQuitActive(widget.services.ipc));
|
||||
return null;
|
||||
},
|
||||
),
|
||||
FindInFilesIntent: CallbackAction<FindInFilesIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.arrangement.setVisible(Slots.sidebar, true);
|
||||
widget.services.arrangement.setCollapsed(Slots.sidebar, false);
|
||||
widget.services.panels.activateTab(Slots.sidebar, 'search.findInFiles');
|
||||
return null;
|
||||
},
|
||||
),
|
||||
FocusNextPanelIntent: CallbackAction<FocusNextPanelIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.focus.focusNextSlot();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
FocusPreviousPanelIntent: CallbackAction<FocusPreviousPanelIntent>(
|
||||
onInvoke: (_) {
|
||||
widget.services.focus.focusPreviousSlot();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
},
|
||||
child: KeyboardListener(
|
||||
focusNode: _keyFocus,
|
||||
autofocus: true,
|
||||
onKeyEvent: _onKey,
|
||||
child: ColoredBox(
|
||||
color: tokens.globalBackground,
|
||||
child: ClideResizeBorder(
|
||||
windowControls: widget.services.window,
|
||||
child: Column(
|
||||
children: [
|
||||
HatBar(kernel: widget.services, menuBar: _menuBar),
|
||||
Expanded(
|
||||
child: DialogHost(
|
||||
router: widget.services.dialog,
|
||||
child: Stack(
|
||||
children: [
|
||||
const Positioned.fill(child: RootLayout()),
|
||||
const ClidePalette(),
|
||||
const QuickOpenOverlay(),
|
||||
const ExLineOverlay(),
|
||||
const Positioned.fill(child: _WelcomeOverlay()),
|
||||
const ToastOverlay(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -13,6 +13,7 @@ export 'src/clide_card_metrics.dart';
|
||||
export 'src/clide_collapser_card.dart';
|
||||
export 'src/clide_code_block.dart';
|
||||
export 'src/clide_divider.dart';
|
||||
export 'src/clide_settings.dart';
|
||||
export 'src/clide_file_image.dart';
|
||||
export 'src/ex_line_overlay.dart';
|
||||
export 'src/clide_filter_box.dart';
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:clide/widgets/widgets.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
/// The ClideSettings.fonts facade (D-101): reads the active families from a
|
||||
/// [ClideSettingsScope], or the bundled defaults outside one.
|
||||
void main() {
|
||||
testWidgets('fonts fall back to the bundled defaults without a scope', (tester) async {
|
||||
late String mono;
|
||||
late String ui;
|
||||
await tester.pumpWidget(
|
||||
Builder(
|
||||
builder: (context) {
|
||||
mono = ClideSettings.fonts.monoOf(context);
|
||||
ui = ClideSettings.fonts.uiOf(context);
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(mono, clideMonoFamily);
|
||||
expect(ui, clideUiFamily);
|
||||
});
|
||||
|
||||
testWidgets('a scope provides the active families, read live', (tester) async {
|
||||
late String mono;
|
||||
late String ui;
|
||||
await tester.pumpWidget(
|
||||
ClideSettingsScope(
|
||||
ui: 'Inter',
|
||||
mono: 'FiraMono',
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
mono = ClideSettings.fonts.monoOf(context);
|
||||
ui = ClideSettings.fonts.uiOf(context);
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(mono, 'FiraMono');
|
||||
expect(ui, 'Inter');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user