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
@@ -5923,3 +5923,7 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD91A7VEW3VCY7QX1END2Z3G', 'status', 'in_progress', 'done', NULL, '2026-06-17 18:14:38', '2026-06-17 18:14:38.272', '2026-06-17 18:14:38.272', NULL, '9c7cb301a7539212e90bf16210684d97', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD91A7VEW3VCY7QX1END2Z3G', 'status', 'in_progress', 'done', NULL, '2026-06-17 18:14:38', '2026-06-17 18:14:38.272', '2026-06-17 18:14:38.272', NULL, '9c7cb301a7539212e90bf16210684d97', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDJ17C3GZWNE98NRVXP189C', 'status', 'backlog', 'in_progress', NULL, '2026-06-17 18:18:13', '2026-06-17 18:18:13.607', '2026-06-17 18:18:13.607', NULL, 'b8f8460933a4b9393acc816c167d725d', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDJ17C3GZWNE98NRVXP189C', 'status', 'backlog', 'in_progress', NULL, '2026-06-17 18:18:13', '2026-06-17 18:18:13.607', '2026-06-17 18:18:13.607', NULL, 'b8f8460933a4b9393acc816c167d725d', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDJ17C3GZWNE98NRVXP189C', 'status', 'in_progress', 'done', NULL, '2026-06-17 18:59:09', '2026-06-17 18:59:09.679', '2026-06-17 18:59:09.679', NULL, 'a088e76684e5d0c4922c62082802dd2d', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDJ17C3GZWNE98NRVXP189C', 'status', 'in_progress', 'done', NULL, '2026-06-17 18:59:09', '2026-06-17 18:59:09.679', '2026-06-17 18:59:09.679', NULL, 'a088e76684e5d0c4922c62082802dd2d', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDTCGG5Z0KNQKF89W1VZSQ8', 'description', NULL, 'Per D-101. The 11 sites that need a BuildContext threaded from the nearest context-bearing caller (resolve ClideSettings.fonts.monoOf(context) and pass the family in): prompt_card.dart toolEditBody/toolReadLikeBody/toolPathLine (top-level fns); conversation_view.dart _toolResult; welcome_view.dart _tipRow; search_panel_view.dart _base getter; clide_markdown.dart _inlineElementSpan/_recordLinkSpan/_fileLinkSpan (static span chain). Until done, these render the default JetBrains Mono regardless of the mono-font setting.', NULL, '2026-06-17 19:03:19', '2026-06-17 19:03:19.250', '2026-06-17 19:03:19.250', NULL, 'dcdd0348a9cd88a23ac1b3711d554c58', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDX5GVH3FTCVDEC1QAFACY4', 'description', NULL, 'Per D-101. Migrate ClideTheme.of(context) consumers (hundreds of sites) to ClideSettings.theme.of(context). The facade already delegates to ClideTheme, so this is a mechanical, no-behavior-change sweep — do it in focused batches with analyze between, ideally via a scripted find/replace + import fixups.', NULL, '2026-06-17 19:03:21', '2026-06-17 19:03:21.095', '2026-06-17 19:03:21.095', NULL, '45e0fb561bb187552a423d098aeb1cb5', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDX5KEAJ8KRWVV7ZSG6H7A0', 'description', NULL, 'Per D-101. Migrate i18n reads (ClideKernel.of(context).i18n[.string]) to ClideSettings.i18n.of(context). The facade delegates to the same I18n service, so behaviour is unchanged; mechanical sweep in batches.', NULL, '2026-06-17 19:03:22', '2026-06-17 19:03:22.235', '2026-06-17 19:03:22.235', NULL, '5d617ff30e22d3b4c57ca6325920d91a', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDTCGG5Z0KNQKF89W1VZSQ8', 'status', 'backlog', 'done', NULL, '2026-06-17 19:35:22', '2026-06-17 19:35:22.690', '2026-06-17 19:35:22.690', NULL, '580448456994d6acecc88cfbd40a595f', 2) ON CONFLICT(hash) DO NOTHING;
+4
View File
@@ -7242,3 +7242,7 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat
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.', 'done', 'medium', NULL, NULL, NULL, '2026-06-17 18:11:42.049', '2026-06-17 18:59:09.679', NULL, 'e81367cebfa243d563325587539f0a3c', 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.', 'done', 'medium', NULL, NULL, NULL, '2026-06-17 18:11:42.049', '2026-06-17 18:59:09.679', NULL, 'e81367cebfa243d563325587539f0a3c', 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 ('06FDDX5GVH3FTCVDEC1QAFACY4', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Migrate theme consumers (ClideTheme.of → ClideSettings.theme.of) per D-101', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-17 19:00:20.828', '2026-06-17 19:00:20.828', NULL, '0a090c8cf04b03de3b82f2e6ea3029de', 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 ('06FDDX5GVH3FTCVDEC1QAFACY4', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Migrate theme consumers (ClideTheme.of → ClideSettings.theme.of) per D-101', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-17 19:00:20.828', '2026-06-17 19:00:20.828', NULL, '0a090c8cf04b03de3b82f2e6ea3029de', 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 ('06FDDX5KEAJ8KRWVV7ZSG6H7A0', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Migrate i18n consumers (ClideKernel.i18n → ClideSettings.i18n.of) per D-101', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-17 19:00:21.490', '2026-06-17 19:00:21.490', NULL, '273598190d1cafe66e693899852916e7', 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 ('06FDDX5KEAJ8KRWVV7ZSG6H7A0', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Migrate i18n consumers (ClideKernel.i18n → ClideSettings.i18n.of) per D-101', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-17 19:00:21.490', '2026-06-17 19:00:21.490', NULL, '273598190d1cafe66e693899852916e7', 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)', 'Per D-101. The 11 sites that need a BuildContext threaded from the nearest context-bearing caller (resolve ClideSettings.fonts.monoOf(context) and pass the family in): prompt_card.dart toolEditBody/toolReadLikeBody/toolPathLine (top-level fns); conversation_view.dart _toolResult; welcome_view.dart _tipRow; search_panel_view.dart _base getter; clide_markdown.dart _inlineElementSpan/_recordLinkSpan/_fileLinkSpan (static span chain). Until done, these render the default JetBrains Mono regardless of the mono-font setting.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-17 18:48:11.649', '2026-06-17 19:03:19.250', NULL, 'fb570bedf09fd60eb835f107310766d4', 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 ('06FDDX5GVH3FTCVDEC1QAFACY4', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Migrate theme consumers (ClideTheme.of → ClideSettings.theme.of) per D-101', 'Per D-101. Migrate ClideTheme.of(context) consumers (hundreds of sites) to ClideSettings.theme.of(context). The facade already delegates to ClideTheme, so this is a mechanical, no-behavior-change sweep — do it in focused batches with analyze between, ideally via a scripted find/replace + import fixups.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-17 19:00:20.828', '2026-06-17 19:03:21.095', NULL, 'b9144ecc303e9223fe212b17ee79c9d8', 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 ('06FDDX5KEAJ8KRWVV7ZSG6H7A0', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Migrate i18n consumers (ClideKernel.i18n → ClideSettings.i18n.of) per D-101', 'Per D-101. Migrate i18n reads (ClideKernel.of(context).i18n[.string]) to ClideSettings.i18n.of(context). The facade delegates to the same I18n service, so behaviour is unchanged; mechanical sweep in batches.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-17 19:00:21.490', '2026-06-17 19:03:22.235', NULL, '16d6229039400bd826a77a08eca9573b', 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)', 'Per D-101. The 11 sites that need a BuildContext threaded from the nearest context-bearing caller (resolve ClideSettings.fonts.monoOf(context) and pass the family in): prompt_card.dart toolEditBody/toolReadLikeBody/toolPathLine (top-level fns); conversation_view.dart _toolResult; welcome_view.dart _tipRow; search_panel_view.dart _base getter; clide_markdown.dart _inlineElementSpan/_recordLinkSpan/_fileLinkSpan (static span chain). Until done, these render the default JetBrains Mono regardless of the mono-font setting.', 'done', 'medium', NULL, NULL, NULL, '2026-06-17 18:48:11.649', '2026-06-17 19:35:22.690', NULL, '7dd6dae4197fc95d0bc6becfad4eecbb', 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);
+7
View File
@@ -79,6 +79,13 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
changed from "Settings…" to "Theme…" so it no longer collides with the new changed from "Settings…" to "Theme…" so it no longer collides with the new
Settings panel in the palette; behaviour is unchanged. (T-445) Settings panel in the palette; behaviour is unchanged. (T-445)
### Fixed
- **Monospace setting now applies everywhere.** Eleven context-free render
helpers (Claude tool bodies + results, inline markdown code/refs, search
previews, welcome tips) hard-coded JetBrains Mono and ignored the Monospace
font setting; they now honour it live. (T-472)
## [2.6.0] — 2026-06-16 ## [2.6.0] — 2026-06-16
### Added ### Added
+13 -3
View File
@@ -336,6 +336,7 @@ class _ConversationViewState extends State<ConversationView> {
key: ValueKey('turn.${item.uuid}'), key: ValueKey('turn.${item.uuid}'),
item: item, item: item,
tokens: tokens, tokens: tokens,
mono: ClideSettings.fonts.monoOf(context),
collapseTools: true, collapseTools: true,
toolUseOutcomes: widget.toolUseOutcomes, toolUseOutcomes: widget.toolUseOutcomes,
quietErrorToolUseIds: widget.quietErrorToolUseIds, quietErrorToolUseIds: widget.quietErrorToolUseIds,
@@ -547,6 +548,7 @@ class _ConversationTurn extends StatelessWidget {
super.key, super.key,
required this.item, required this.item,
required this.tokens, required this.tokens,
required this.mono,
this.collapseTools = false, this.collapseTools = false,
this.toolUseOutcomes = const <String, bool>{}, this.toolUseOutcomes = const <String, bool>{},
this.quietErrorToolUseIds = const <String>{}, this.quietErrorToolUseIds = const <String>{},
@@ -560,6 +562,11 @@ class _ConversationTurn extends StatelessWidget {
final ConversationItem item; final ConversationItem item;
final SurfaceTokens tokens; 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 /// 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 / /// 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 /// edit collapser), it renders the bare inner content card so collapsers
@@ -788,6 +795,7 @@ class _ConversationTurn extends StatelessWidget {
key: ValueKey('run.${r.uuid}'), key: ValueKey('run.${r.uuid}'),
item: r, item: r,
tokens: tokens, tokens: tokens,
mono: mono,
toolUseOutcomes: toolUseOutcomes, toolUseOutcomes: toolUseOutcomes,
quietErrorToolUseIds: quietErrorToolUseIds, quietErrorToolUseIds: quietErrorToolUseIds,
toolUseById: toolUseById, toolUseById: toolUseById,
@@ -948,7 +956,7 @@ class _ConversationTurn extends StatelessWidget {
label: t.name, label: t.name,
copyText: const JsonEncoder.withIndent(' ').convert(t.input), copyText: const JsonEncoder.withIndent(' ').convert(t.input),
status: status, status: status,
body: toolInputBody(tokens, t.name, t.input), body: toolInputBody(tokens, t.name, t.input, mono),
extraSegments: segments, extraSegments: segments,
// Inside a collapser the surrounding padding is even on all sides // 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 // (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, collapsible: quiet || multiline,
collapsedByDefault: quiet, // genuine errors stay expanded; a denial folds collapsedByDefault: quiet, // genuine errors stay expanded; a denial folds
collapsedSummary: (quiet || multiline) ? _firstLine(t.content) : null, 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, collapsedSummary: multiline ? _firstLine(t.content) : null,
body: isOutputTool body: isOutputTool
? ClideCodeBlock(source: t.content, language: 'text') ? 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}'), key: ValueKey('step.${item.uuid}'),
item: item, item: item,
tokens: tokens, tokens: tokens,
mono: ClideSettings.fonts.monoOf(context),
toolUseOutcomes: toolUseOutcomes, toolUseOutcomes: toolUseOutcomes,
quietErrorToolUseIds: quietErrorToolUseIds, quietErrorToolUseIds: quietErrorToolUseIds,
toolUseById: toolUseById, toolUseById: toolUseById,
@@ -1154,6 +1163,7 @@ class _EditRunCard extends StatelessWidget {
key: ValueKey('edit.${item.uuid}'), key: ValueKey('edit.${item.uuid}'),
item: item, item: item,
tokens: tokens, tokens: tokens,
mono: ClideSettings.fonts.monoOf(context),
toolUseOutcomes: toolUseOutcomes, toolUseOutcomes: toolUseOutcomes,
quietErrorToolUseIds: quietErrorToolUseIds, quietErrorToolUseIds: quietErrorToolUseIds,
toolUseById: toolUseById, 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). // or file body is visible but doesn't swamp the composer zone (D-78).
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200), 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), const SizedBox(height: 10),
Wrap( Wrap(
@@ -284,7 +284,7 @@ class _ToolPromptCardState extends State<ToolPromptCard> {
/// Render the tool input in the shape that best fits the tool. Delegates to /// 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). /// 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 -------------- // -- AskUserQuestion: single = bare, multi = stepper + review --------------
@@ -492,19 +492,19 @@ class _ToolPromptCardState extends State<ToolPromptCard> {
/// ///
/// Shared between [ToolPromptCard] (permission prompt body) and the /// Shared between [ToolPromptCard] (permission prompt body) and the
/// [ConversationView] tool-use card bodies (T-168). /// [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) { switch (toolName) {
case 'Bash': case 'Bash':
return toolBashBody(tokens, input); return toolBashBody(tokens, input);
case 'Write': case 'Write':
return toolWriteBody(tokens, input); return toolWriteBody(tokens, input, mono);
case 'Edit': case 'Edit':
case 'MultiEdit': case 'MultiEdit':
return toolEditBody(tokens, input); return toolEditBody(tokens, input, mono);
case 'Read': case 'Read':
case 'Grep': case 'Grep':
case 'LS': case 'LS':
return toolReadLikeBody(tokens, toolName, input); return toolReadLikeBody(tokens, toolName, input, mono);
default: default:
return ClideCodeBlock(source: const JsonEncoder.withIndent(' ').convert(input), language: 'json'); 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. /// 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 path = input['file_path'] as String? ?? '';
final content = input['content'] as String? ?? ''; final content = input['content'] as String? ?? '';
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (path.isNotEmpty) toolPathLine(tokens, path), if (path.isNotEmpty) toolPathLine(tokens, path, mono),
ClideCodeBlock(source: content, language: grammarForPath(path)), ClideCodeBlock(source: content, language: grammarForPath(path)),
], ],
); );
} }
/// Edit / MultiEdit tool body: before/after diff view. /// 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 path = input['file_path'] as String? ?? '';
final oldStr = input['old_string'] as String? ?? ''; final oldStr = input['old_string'] as String? ?? '';
final newStr = input['new_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, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (path.isNotEmpty) toolPathLine(tokens, path), if (path.isNotEmpty) toolPathLine(tokens, path, mono),
ClideText('— before', fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), ClideText('— before', fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: mono),
const SizedBox(height: 4), const SizedBox(height: 4),
ClideCodeBlock(source: oldStr, language: lang), ClideCodeBlock(source: oldStr, language: lang),
const SizedBox(height: 8), 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), const SizedBox(height: 4),
ClideCodeBlock(source: newStr, language: lang), 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 /// 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 /// so the card stays compact. These tools produce the interesting output in the
/// result card rather than their input. /// 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 path = input['file_path'] ?? input['path'] ?? input['pattern'] ?? '';
final extra = <String>[]; final extra = <String>[];
if (toolName == 'Grep') { if (toolName == 'Grep') {
@@ -576,13 +576,13 @@ Widget toolReadLikeBody(SurfaceTokens tokens, String toolName, Map<String, dynam
if (pat != null && pat.isNotEmpty) extra.add('"$pat"'); if (pat != null && pat.isNotEmpty) extra.add('"$pat"');
} }
final label = [path.toString(), ...extra].where((s) => s.isNotEmpty).join(' '); 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. /// 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), 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 ------------------------------------------- // -- shared note / free-text field -------------------------------------------
@@ -346,7 +346,7 @@ class _MatchRow extends StatelessWidget {
width: 36, width: 36,
child: ClideText('${match.line}', fontSize: clideFontCaption, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.globalTextMuted), 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 line = match.preview;
final start = match.matchStart.clamp(0, line.length); final start = match.matchStart.clamp(0, line.length);
final end = match.matchEnd.clamp(start, line.length); final end = match.matchEnd.clamp(start, line.length);
@@ -364,12 +364,12 @@ class _MatchRow extends StatelessWidget {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
text: TextSpan( text: TextSpan(
style: _base, style: _base(mono),
children: [ children: [
TextSpan(text: line.substring(0, start)), TextSpan(text: line.substring(0, start)),
TextSpan( TextSpan(
text: line.substring(start, end), 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)), TextSpan(text: line.substring(end)),
], ],
@@ -379,7 +379,7 @@ class _MatchRow extends StatelessWidget {
/// Replace-preview: the original line struck through, then the /// Replace-preview: the original line struck through, then the
/// rewritten line (computed with the same engine the apply uses). /// rewritten line (computed with the same engine the apply uses).
Widget _preview() { Widget _preview(String mono) {
final after = applyToText(match.preview, query, replacement).text; final after = applyToText(match.preview, query, replacement).text;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -389,7 +389,7 @@ class _MatchRow extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
text: TextSpan( text: TextSpan(
text: match.preview, text: match.preview,
style: _base.copyWith(decoration: TextDecoration.lineThrough, color: tokens.globalTextMuted), style: _base(mono).copyWith(decoration: TextDecoration.lineThrough, color: tokens.globalTextMuted),
), ),
), ),
RichText( RichText(
@@ -397,7 +397,7 @@ class _MatchRow extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
text: TextSpan( text: TextSpan(
text: after, 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: [ children: [
ClideText('TIPS', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: ClideSettings.fonts.monoOf(context)), ClideText('TIPS', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: ClideSettings.fonts.monoOf(context)),
const SizedBox(height: 14), const SizedBox(height: 14),
_tipRow(firstRow), _tipRow(firstRow, ClideSettings.fonts.monoOf(context)),
const SizedBox(height: 8), 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( return Row(
children: [ children: [
for (var i = 0; i < tips.length; i++) ...[ for (var i = 0; i < tips.length; i++) ...[
@@ -126,7 +126,7 @@ class _TipsCard extends StatelessWidget {
Expanded( Expanded(
child: ClideText(tips[i].$1, fontSize: clideFontMeta, color: tokens.globalTextMuted), 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),
], ],
), ),
), ),
+27 -11
View File
@@ -2,6 +2,7 @@ import 'package:clide/kernel/src/theme/controller.dart';
import 'package:clide/kernel/src/theme/tokens.dart'; import 'package:clide/kernel/src/theme/tokens.dart';
import 'package:clide/widgets/src/clide_code_block.dart'; import 'package:clide/widgets/src/clide_code_block.dart';
import 'package:clide/widgets/src/clide_divider.dart'; import 'package:clide/widgets/src/clide_divider.dart';
import 'package:clide/widgets/src/clide_settings.dart';
import 'package:clide/widgets/src/clide_text.dart'; import 'package:clide/widgets/src/clide_text.dart';
import 'package:clide/widgets/src/clide_tappable.dart'; import 'package:clide/widgets/src/clide_tappable.dart';
import 'package:clide/widgets/src/typography.dart'; import 'package:clide/widgets/src/typography.dart';
@@ -35,7 +36,7 @@ typedef FileTapCallback = void Function(String path, int? line);
/// inert (the text renders, just not interactive). /// inert (the text renders, just not interactive).
@immutable @immutable
class ClideMarkdownHooks { class ClideMarkdownHooks {
const ClideMarkdownHooks({this.onRecordTap, this.onImageToken, this.onLinkTap, this.resolveFileRef, this.onOpenFile}); const ClideMarkdownHooks({this.onRecordTap, this.onImageToken, this.onLinkTap, this.resolveFileRef, this.onOpenFile, this.mono = clideMonoFamily});
/// Tap a governance/ticket ref (T-281, D-77, …) → open the record (T-279). /// Tap a governance/ticket ref (T-281, D-77, …) → open the record (T-279).
final RecordTapCallback? onRecordTap; final RecordTapCallback? onRecordTap;
@@ -53,6 +54,12 @@ class ClideMarkdownHooks {
/// Open a resolved workspace file in the editor (T-300). /// Open a resolved workspace file in the editor (T-300).
final FileTapCallback? onOpenFile; final FileTapCallback? onOpenFile;
/// The live monospace family (Settings → Appearance, T-471). Resolved once
/// from context at the widget entry and threaded through the static span
/// chain via this carrier, so inline `code` and ref spans honour the setting
/// even though the span builders are context-free statics (T-472).
final String mono;
static const none = ClideMarkdownHooks(); static const none = ClideMarkdownHooks();
} }
@@ -116,6 +123,7 @@ class ClideMarkdown extends StatelessWidget {
onLinkTap: onLinkTap, onLinkTap: onLinkTap,
resolveFileRef: resolveFileRef, resolveFileRef: resolveFileRef,
onOpenFile: onOpenFile, onOpenFile: onOpenFile,
mono: ClideSettings.fonts.monoOf(context),
); );
final widgets = _buildNodes(nodes, tokens, hooks); final widgets = _buildNodes(nodes, tokens, hooks);
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: widgets); return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: widgets);
@@ -372,16 +380,16 @@ class ClideMarkdown extends StatelessWidget {
// editor (T-300); other inline code renders verbatim. // editor (T-300); other inline code renders verbatim.
if (hooks.resolveFileRef != null && hooks.onOpenFile != null) { if (hooks.resolveFileRef != null && hooks.onOpenFile != null) {
final ref = _codeFileRef(raw, hooks.resolveFileRef!); final ref = _codeFileRef(raw, hooks.resolveFileRef!);
if (ref != null) return _fileLinkSpan(raw, ref.$1, ref.$2, tokens, hooks.onOpenFile!, mono: true); if (ref != null) return _fileLinkSpan(raw, ref.$1, ref.$2, tokens, hooks.onOpenFile!, hooks.mono, mono: true);
} }
return TextSpan( return TextSpan(
text: raw, text: raw,
style: TextStyle(fontFamily: clideMonoFamily, fontSize: clideFontMono, color: tokens.syntaxString, backgroundColor: tokens.panelBackground), style: TextStyle(fontFamily: hooks.mono, fontSize: clideFontMono, color: tokens.syntaxString, backgroundColor: tokens.panelBackground),
); );
case 'a': case 'a':
final text = _unescapeHtml(el.textContent); final text = _unescapeHtml(el.textContent);
if (hooks.onRecordTap != null && _recordPattern.hasMatch(text)) { if (hooks.onRecordTap != null && _recordPattern.hasMatch(text)) {
return _recordLinkSpan(text, tokens, hooks.onRecordTap!); return _recordLinkSpan(text, tokens, hooks.onRecordTap!, hooks.mono);
} }
// An http(s) link (explicit or autolinked) → tappable, opens via the // An http(s) link (explicit or autolinked) → tappable, opens via the
// caller's handler (T-253). Non-http schemes stay coloured-but-inert. // caller's handler (T-253). Non-http schemes stay coloured-but-inert.
@@ -394,7 +402,7 @@ class ClideMarkdown extends StatelessWidget {
if (hooks.resolveFileRef != null && hooks.onOpenFile != null && href != null) { if (hooks.resolveFileRef != null && hooks.onOpenFile != null && href != null) {
final (path, line) = _splitFileRef(href); final (path, line) = _splitFileRef(href);
final abs = hooks.resolveFileRef!(path); final abs = hooks.resolveFileRef!(path);
if (abs != null) return _fileLinkSpan(text, abs, line, tokens, hooks.onOpenFile!); if (abs != null) return _fileLinkSpan(text, abs, line, tokens, hooks.onOpenFile!, hooks.mono);
} }
return TextSpan( return TextSpan(
text: text, text: text,
@@ -467,7 +475,7 @@ class ClideMarkdown extends StatelessWidget {
final hits = <_LinkHit>[]; final hits = <_LinkHit>[];
if (wantRecords) { if (wantRecords) {
for (final m in _bareRecordPattern.allMatches(text)) { for (final m in _bareRecordPattern.allMatches(text)) {
hits.add(_LinkHit(m.start, m.end, _recordLinkSpan(m[0]!, tokens, hooks.onRecordTap!))); hits.add(_LinkHit(m.start, m.end, _recordLinkSpan(m[0]!, tokens, hooks.onRecordTap!, hooks.mono)));
} }
} }
if (wantFiles) { if (wantFiles) {
@@ -475,7 +483,7 @@ class ClideMarkdown extends StatelessWidget {
final abs = hooks.resolveFileRef!(m.group(1)!); final abs = hooks.resolveFileRef!(m.group(1)!);
if (abs == null) continue; if (abs == null) continue;
final line = m.group(2) == null ? null : int.tryParse(m.group(2)!); final line = m.group(2) == null ? null : int.tryParse(m.group(2)!);
hits.add(_LinkHit(m.start, m.end, _fileLinkSpan(text.substring(m.start, m.end), abs, line, tokens, hooks.onOpenFile!))); hits.add(_LinkHit(m.start, m.end, _fileLinkSpan(text.substring(m.start, m.end), abs, line, tokens, hooks.onOpenFile!, hooks.mono)));
} }
} }
if (hits.isEmpty) return [TextSpan(text: text)]; if (hits.isEmpty) return [TextSpan(text: text)];
@@ -517,7 +525,7 @@ class ClideMarkdown extends StatelessWidget {
/// A clickable record-reference span: [id] rendered in the focus accent with /// A clickable record-reference span: [id] rendered in the focus accent with
/// a hover underline, firing [onRecordTap] on tap (T-279). Shared by bare-text /// a hover underline, firing [onRecordTap] on tap (T-279). Shared by bare-text
/// refs and record-shaped markdown links so both look and behave alike. /// refs and record-shaped markdown links so both look and behave alike.
static InlineSpan _recordLinkSpan(String id, SurfaceTokens tokens, RecordTapCallback onRecordTap) { static InlineSpan _recordLinkSpan(String id, SurfaceTokens tokens, RecordTapCallback onRecordTap, String mono) {
return WidgetSpan( return WidgetSpan(
alignment: PlaceholderAlignment.baseline, alignment: PlaceholderAlignment.baseline,
baseline: TextBaseline.alphabetic, baseline: TextBaseline.alphabetic,
@@ -529,7 +537,7 @@ class ClideMarkdown extends StatelessWidget {
color: tokens.globalFocus, color: tokens.globalFocus,
fontSize: _fontSize, fontSize: _fontSize,
height: _lineHeight, height: _lineHeight,
fontFamily: clideMonoFamily, fontFamily: mono,
decoration: hovered ? TextDecoration.underline : null, decoration: hovered ? TextDecoration.underline : null,
decorationColor: tokens.globalFocus, decorationColor: tokens.globalFocus,
), ),
@@ -547,7 +555,15 @@ class ClideMarkdown extends StatelessWidget {
/// the focus accent, underlined on hover, opening the resolved [absPath] at /// the focus accent, underlined on hover, opening the resolved [absPath] at
/// [line] (when present) in the editor via [onOpenFile]. The [mono] flag keeps /// [line] (when present) in the editor via [onOpenFile]. The [mono] flag keeps
/// backticked refs in the monospace face; prose refs use the UI face. /// backticked refs in the monospace face; prose refs use the UI face.
static InlineSpan _fileLinkSpan(String display, String absPath, int? line, SurfaceTokens tokens, FileTapCallback onOpenFile, {bool mono = false}) { static InlineSpan _fileLinkSpan(
String display,
String absPath,
int? line,
SurfaceTokens tokens,
FileTapCallback onOpenFile,
String monoFamily, {
bool mono = false,
}) {
return WidgetSpan( return WidgetSpan(
alignment: PlaceholderAlignment.baseline, alignment: PlaceholderAlignment.baseline,
baseline: TextBaseline.alphabetic, baseline: TextBaseline.alphabetic,
@@ -560,7 +576,7 @@ class ClideMarkdown extends StatelessWidget {
color: tokens.globalFocus, color: tokens.globalFocus,
fontSize: mono ? clideFontMono : _fontSize, fontSize: mono ? clideFontMono : _fontSize,
height: _lineHeight, height: _lineHeight,
fontFamily: mono ? clideMonoFamily : clideUiFamily, fontFamily: mono ? monoFamily : clideUiFamily,
fontFamilyFallback: mono ? null : clideUiFamilyFallback, fontFamilyFallback: mono ? null : clideUiFamilyFallback,
decoration: hovered ? TextDecoration.underline : null, decoration: hovered ? TextDecoration.underline : null,
decorationColor: tokens.globalFocus, decorationColor: tokens.globalFocus,