fix(fonts): honour the monospace setting in context-free render helpers (T-472)

Eleven leaf sites rendered monospace text from top-level/static helpers that
take no BuildContext, so they hard-coded clideMonoFamily and ignored the
Settings → Appearance Monospace choice that the D-101 facade made live
everywhere else.

Thread the resolved family in from the nearest context-bearing caller:
- claude tool bodies/results: a required `mono` field on _ConversationTurn +
  a `mono` arg on the shared toolInputBody chain;
- markdown inline `code`/record/file-ref spans: carried on ClideMarkdownHooks,
  which build() already constructs from context and threads to every static;
- search preview styles and welcome tips: a `mono` parameter on the helpers.

No behaviour change when the setting is default; these surfaces now switch
live with the rest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 21:35:38 +02:00
co-authored by Claude Opus 4.8
parent 6b5317342b
commit c4248e3228
8 changed files with 83 additions and 42 deletions
+13 -3
View File
@@ -336,6 +336,7 @@ class _ConversationViewState extends State<ConversationView> {
key: ValueKey('turn.${item.uuid}'),
item: item,
tokens: tokens,
mono: ClideSettings.fonts.monoOf(context),
collapseTools: true,
toolUseOutcomes: widget.toolUseOutcomes,
quietErrorToolUseIds: widget.quietErrorToolUseIds,
@@ -547,6 +548,7 @@ class _ConversationTurn extends StatelessWidget {
super.key,
required this.item,
required this.tokens,
required this.mono,
this.collapseTools = false,
this.toolUseOutcomes = const <String, bool>{},
this.quietErrorToolUseIds = const <String>{},
@@ -560,6 +562,11 @@ class _ConversationTurn extends StatelessWidget {
final ConversationItem item;
final SurfaceTokens tokens;
/// The live monospace family (T-471/T-472), resolved from context by the
/// parent and threaded in so the context-free tool-body/result helpers honour
/// the Settings → Appearance choice.
final String mono;
/// When true (top-level stream items), a tool use renders as its own
/// collapser over a one-item list (T-305). When false (already inside a run /
/// edit collapser), it renders the bare inner content card so collapsers
@@ -788,6 +795,7 @@ class _ConversationTurn extends StatelessWidget {
key: ValueKey('run.${r.uuid}'),
item: r,
tokens: tokens,
mono: mono,
toolUseOutcomes: toolUseOutcomes,
quietErrorToolUseIds: quietErrorToolUseIds,
toolUseById: toolUseById,
@@ -948,7 +956,7 @@ class _ConversationTurn extends StatelessWidget {
label: t.name,
copyText: const JsonEncoder.withIndent(' ').convert(t.input),
status: status,
body: toolInputBody(tokens, t.name, t.input),
body: toolInputBody(tokens, t.name, t.input, mono),
extraSegments: segments,
// Inside a collapser the surrounding padding is even on all sides
// (T-305): a matching bottom margin is the canvas's bottom inset and the
@@ -1005,7 +1013,7 @@ class _ConversationTurn extends StatelessWidget {
collapsible: quiet || multiline,
collapsedByDefault: quiet, // genuine errors stay expanded; a denial folds
collapsedSummary: (quiet || multiline) ? _firstLine(t.content) : null,
body: ClideText(t.content, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: quiet ? tokens.globalTextMuted : tokens.statusError),
body: ClideText(t.content, fontSize: clideFontMeta, fontFamily: mono, color: quiet ? tokens.globalTextMuted : tokens.statusError),
);
}
@@ -1026,7 +1034,7 @@ class _ConversationTurn extends StatelessWidget {
collapsedSummary: multiline ? _firstLine(t.content) : null,
body: isOutputTool
? ClideCodeBlock(source: t.content, language: 'text')
: ClideText(t.content, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalForeground),
: ClideText(t.content, fontSize: clideFontMeta, fontFamily: mono, color: tokens.globalForeground),
);
}
@@ -1089,6 +1097,7 @@ class _ActivityCard extends StatelessWidget {
key: ValueKey('step.${item.uuid}'),
item: item,
tokens: tokens,
mono: ClideSettings.fonts.monoOf(context),
toolUseOutcomes: toolUseOutcomes,
quietErrorToolUseIds: quietErrorToolUseIds,
toolUseById: toolUseById,
@@ -1154,6 +1163,7 @@ class _EditRunCard extends StatelessWidget {
key: ValueKey('edit.${item.uuid}'),
item: item,
tokens: tokens,
mono: ClideSettings.fonts.monoOf(context),
toolUseOutcomes: toolUseOutcomes,
quietErrorToolUseIds: quietErrorToolUseIds,
toolUseById: toolUseById,
+16 -16
View File
@@ -259,7 +259,7 @@ class _ToolPromptCardState extends State<ToolPromptCard> {
// or file body is visible but doesn't swamp the composer zone (D-78).
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView(child: _inputBody(tokens, p)),
child: SingleChildScrollView(child: _inputBody(tokens, ClideSettings.fonts.monoOf(context), p)),
),
const SizedBox(height: 10),
Wrap(
@@ -284,7 +284,7 @@ class _ToolPromptCardState extends State<ToolPromptCard> {
/// Render the tool input in the shape that best fits the tool. Delegates to
/// the shared top-level helpers (also used by ConversationView — T-168).
Widget _inputBody(SurfaceTokens tokens, ToolPrompt p) => toolInputBody(tokens, p.toolName, p.input);
Widget _inputBody(SurfaceTokens tokens, String mono, ToolPrompt p) => toolInputBody(tokens, p.toolName, p.input, mono);
// -- AskUserQuestion: single = bare, multi = stepper + review --------------
@@ -492,19 +492,19 @@ class _ToolPromptCardState extends State<ToolPromptCard> {
///
/// Shared between [ToolPromptCard] (permission prompt body) and the
/// [ConversationView] tool-use card bodies (T-168).
Widget toolInputBody(SurfaceTokens tokens, String toolName, Map<String, dynamic> input) {
Widget toolInputBody(SurfaceTokens tokens, String toolName, Map<String, dynamic> input, String mono) {
switch (toolName) {
case 'Bash':
return toolBashBody(tokens, input);
case 'Write':
return toolWriteBody(tokens, input);
return toolWriteBody(tokens, input, mono);
case 'Edit':
case 'MultiEdit':
return toolEditBody(tokens, input);
return toolEditBody(tokens, input, mono);
case 'Read':
case 'Grep':
case 'LS':
return toolReadLikeBody(tokens, toolName, input);
return toolReadLikeBody(tokens, toolName, input, mono);
default:
return ClideCodeBlock(source: const JsonEncoder.withIndent(' ').convert(input), language: 'json');
}
@@ -530,21 +530,21 @@ Widget toolBashBody(SurfaceTokens tokens, Map<String, dynamic> input) {
}
/// Write tool body: the file path + content with syntax highlighting.
Widget toolWriteBody(SurfaceTokens tokens, Map<String, dynamic> input) {
Widget toolWriteBody(SurfaceTokens tokens, Map<String, dynamic> input, String mono) {
final path = input['file_path'] as String? ?? '';
final content = input['content'] as String? ?? '';
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
if (path.isNotEmpty) toolPathLine(tokens, path),
if (path.isNotEmpty) toolPathLine(tokens, path, mono),
ClideCodeBlock(source: content, language: grammarForPath(path)),
],
);
}
/// Edit / MultiEdit tool body: before/after diff view.
Widget toolEditBody(SurfaceTokens tokens, Map<String, dynamic> input) {
Widget toolEditBody(SurfaceTokens tokens, Map<String, dynamic> input, String mono) {
final path = input['file_path'] as String? ?? '';
final oldStr = input['old_string'] as String? ?? '';
final newStr = input['new_string'] as String? ?? '';
@@ -553,12 +553,12 @@ Widget toolEditBody(SurfaceTokens tokens, Map<String, dynamic> input) {
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
if (path.isNotEmpty) toolPathLine(tokens, path),
ClideText('— before', fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
if (path.isNotEmpty) toolPathLine(tokens, path, mono),
ClideText('— before', fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: mono),
const SizedBox(height: 4),
ClideCodeBlock(source: oldStr, language: lang),
const SizedBox(height: 8),
ClideText('+ after', fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
ClideText('+ after', fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: mono),
const SizedBox(height: 4),
ClideCodeBlock(source: newStr, language: lang),
],
@@ -568,7 +568,7 @@ Widget toolEditBody(SurfaceTokens tokens, Map<String, dynamic> input) {
/// Read / Grep / LS body: show the file path or pattern as a one-liner label
/// so the card stays compact. These tools produce the interesting output in the
/// result card rather than their input.
Widget toolReadLikeBody(SurfaceTokens tokens, String toolName, Map<String, dynamic> input) {
Widget toolReadLikeBody(SurfaceTokens tokens, String toolName, Map<String, dynamic> input, String mono) {
final path = input['file_path'] ?? input['path'] ?? input['pattern'] ?? '';
final extra = <String>[];
if (toolName == 'Grep') {
@@ -576,13 +576,13 @@ Widget toolReadLikeBody(SurfaceTokens tokens, String toolName, Map<String, dynam
if (pat != null && pat.isNotEmpty) extra.add('"$pat"');
}
final label = [path.toString(), ...extra].where((s) => s.isNotEmpty).join(' ');
return ClideText(label.isNotEmpty ? label : toolName, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalForeground);
return ClideText(label.isNotEmpty ? label : toolName, fontSize: clideFontMeta, fontFamily: mono, color: tokens.globalForeground);
}
/// A muted file path line, shared across tool bodies.
Widget toolPathLine(SurfaceTokens tokens, String path) => Padding(
Widget toolPathLine(SurfaceTokens tokens, String path, String mono) => Padding(
padding: const EdgeInsets.only(bottom: 6),
child: ClideText(path, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalTextMuted),
child: ClideText(path, fontSize: clideFontMeta, fontFamily: mono, color: tokens.globalTextMuted),
);
// -- shared note / free-text field -------------------------------------------
@@ -346,7 +346,7 @@ class _MatchRow extends StatelessWidget {
width: 36,
child: ClideText('${match.line}', fontSize: clideFontCaption, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.globalTextMuted),
),
Expanded(child: replacement.isEmpty ? _highlighted() : _preview()),
Expanded(child: replacement.isEmpty ? _highlighted(ClideSettings.fonts.monoOf(context)) : _preview(ClideSettings.fonts.monoOf(context))),
],
),
),
@@ -354,9 +354,9 @@ class _MatchRow extends StatelessWidget {
);
}
TextStyle get _base => TextStyle(fontFamily: clideMonoFamily, fontSize: clideFontCaption, color: tokens.sidebarForeground);
TextStyle _base(String mono) => TextStyle(fontFamily: mono, fontSize: clideFontCaption, color: tokens.sidebarForeground);
Widget _highlighted() {
Widget _highlighted(String mono) {
final line = match.preview;
final start = match.matchStart.clamp(0, line.length);
final end = match.matchEnd.clamp(start, line.length);
@@ -364,12 +364,12 @@ class _MatchRow extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.ellipsis,
text: TextSpan(
style: _base,
style: _base(mono),
children: [
TextSpan(text: line.substring(0, start)),
TextSpan(
text: line.substring(start, end),
style: _base.copyWith(color: tokens.globalFocus, fontWeight: FontWeight.bold),
style: _base(mono).copyWith(color: tokens.globalFocus, fontWeight: FontWeight.bold),
),
TextSpan(text: line.substring(end)),
],
@@ -379,7 +379,7 @@ class _MatchRow extends StatelessWidget {
/// Replace-preview: the original line struck through, then the
/// rewritten line (computed with the same engine the apply uses).
Widget _preview() {
Widget _preview(String mono) {
final after = applyToText(match.preview, query, replacement).text;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -389,7 +389,7 @@ class _MatchRow extends StatelessWidget {
overflow: TextOverflow.ellipsis,
text: TextSpan(
text: match.preview,
style: _base.copyWith(decoration: TextDecoration.lineThrough, color: tokens.globalTextMuted),
style: _base(mono).copyWith(decoration: TextDecoration.lineThrough, color: tokens.globalTextMuted),
),
),
RichText(
@@ -397,7 +397,7 @@ class _MatchRow extends StatelessWidget {
overflow: TextOverflow.ellipsis,
text: TextSpan(
text: after,
style: _base.copyWith(color: tokens.globalFocus),
style: _base(mono).copyWith(color: tokens.globalFocus),
),
),
],
+4 -4
View File
@@ -107,16 +107,16 @@ class _TipsCard extends StatelessWidget {
children: [
ClideText('TIPS', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: ClideSettings.fonts.monoOf(context)),
const SizedBox(height: 14),
_tipRow(firstRow),
_tipRow(firstRow, ClideSettings.fonts.monoOf(context)),
const SizedBox(height: 8),
_tipRow(secondRow),
_tipRow(secondRow, ClideSettings.fonts.monoOf(context)),
],
),
),
);
}
Widget _tipRow(List<(String, String)> tips) {
Widget _tipRow(List<(String, String)> tips, String mono) {
return Row(
children: [
for (var i = 0; i < tips.length; i++) ...[
@@ -126,7 +126,7 @@ class _TipsCard extends StatelessWidget {
Expanded(
child: ClideText(tips[i].$1, fontSize: clideFontMeta, color: tokens.globalTextMuted),
),
ClideText(tips[i].$2, fontSize: clideFontSmall, color: tokens.globalForeground, fontFamily: clideMonoFamily),
ClideText(tips[i].$2, fontSize: clideFontSmall, color: tokens.globalForeground, fontFamily: mono),
],
),
),