fix(fonts): markdown prose + links honour the live UI font (T-475)
The markdown span builders are context-free statics, so they pinned the bundled clideUiFamily const and ignored the Appearance UI-font setting — Claude's conversation prose stayed on the default face regardless of the pick. Thread the resolved UI family through the same ClideMarkdownHooks carrier already used for mono (T-472): build() resolves it from context and every prose/link span reads hooks.ui. Adds a regression test asserting prose and inline code follow the families from the ClideSettingsScope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5929,3 +5929,4 @@ 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 ('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;
|
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;
|
||||||
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', 'status', 'backlog', 'done', NULL, '2026-06-17 19:37:39', '2026-06-17 19:37:39.813', '2026-06-17 19:37:39.813', NULL, 'd59d0df2b693a0616265dd5e39559dc0', 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', 'status', 'backlog', 'done', NULL, '2026-06-17 19:37:39', '2026-06-17 19:37:39.813', '2026-06-17 19:37:39.813', NULL, 'd59d0df2b693a0616265dd5e39559dc0', 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', 'status', 'backlog', 'done', NULL, '2026-06-17 19:47:02', '2026-06-17 19:47:02.941', '2026-06-17 19:47:02.941', NULL, '28625100d88cacac15cabe97754fcdc5', 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', 'status', 'backlog', 'done', NULL, '2026-06-17 19:47:02', '2026-06-17 19:47:02.941', '2026-06-17 19:47:02.941', NULL, '28625100d88cacac15cabe97754fcdc5', 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 ('06FDEMY3DQWDNY535PMMSB0CSW', 'status', 'backlog', 'done', NULL, '2026-06-17 20:51:21', '2026-06-17 20:51:21.006', '2026-06-17 20:51:21.006', NULL, '20516cde17cc921f419aa5f12ea49bc1', 2) ON CONFLICT(hash) DO NOTHING;
|
||||||
|
|||||||
@@ -7248,3 +7248,5 @@ 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 ('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);
|
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);
|
||||||
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.', 'done', 'medium', NULL, NULL, NULL, '2026-06-17 19:00:21.490', '2026-06-17 19:37:39.813', NULL, 'fc64918991f0c9133419ee51227e1939', 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.', 'done', 'medium', NULL, NULL, NULL, '2026-06-17 19:00:21.490', '2026-06-17 19:37:39.813', NULL, 'fc64918991f0c9133419ee51227e1939', 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.', 'done', 'medium', NULL, NULL, NULL, '2026-06-17 19:00:20.828', '2026-06-17 19:47:02.941', NULL, '8ea58b6162c81778a610f46b07585b05', 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.', 'done', 'medium', NULL, NULL, NULL, '2026-06-17 19:00:20.828', '2026-06-17 19:47:02.941', NULL, '8ea58b6162c81778a610f46b07585b05', 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 ('06FDEMY3DQWDNY535PMMSB0CSW', 'bug', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Markdown prose ignores the UI-font setting (clideUiFamily const)', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-17 20:44:11.501', '2026-06-17 20:44:11.501', NULL, '5861faa02d7cc34271dc72cf964f63cf', 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 ('06FDEMY3DQWDNY535PMMSB0CSW', 'bug', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Markdown prose ignores the UI-font setting (clideUiFamily const)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-06-17 20:44:11.501', '2026-06-17 20:51:21.006', NULL, '6d53da1fcfe6ad27b54a6ea07c1ccc7e', 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);
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- **Markdown prose honours the UI-font setting.** Claude's conversation prose
|
||||||
|
and inline links render through the markdown engine, which pinned the bundled
|
||||||
|
UI face and ignored the Appearance UI-font pick; it now follows the setting
|
||||||
|
live, like the rest of the app. (T-475)
|
||||||
|
|
||||||
## [2.7.0] — 2026-06-17
|
## [2.7.0] — 2026-06-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -35,7 +35,15 @@ 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, this.mono = clideMonoFamily});
|
const ClideMarkdownHooks({
|
||||||
|
this.onRecordTap,
|
||||||
|
this.onImageToken,
|
||||||
|
this.onLinkTap,
|
||||||
|
this.resolveFileRef,
|
||||||
|
this.onOpenFile,
|
||||||
|
this.mono = clideMonoFamily,
|
||||||
|
this.ui = clideUiFamily,
|
||||||
|
});
|
||||||
|
|
||||||
/// 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;
|
||||||
@@ -59,6 +67,11 @@ class ClideMarkdownHooks {
|
|||||||
/// even though the span builders are context-free statics (T-472).
|
/// even though the span builders are context-free statics (T-472).
|
||||||
final String mono;
|
final String mono;
|
||||||
|
|
||||||
|
/// The live UI family (Settings → Appearance, T-460), threaded the same way
|
||||||
|
/// so markdown prose and link spans honour the chosen UI font instead of
|
||||||
|
/// pinning the bundled default (T-475).
|
||||||
|
final String ui;
|
||||||
|
|
||||||
static const none = ClideMarkdownHooks();
|
static const none = ClideMarkdownHooks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +136,7 @@ class ClideMarkdown extends StatelessWidget {
|
|||||||
resolveFileRef: resolveFileRef,
|
resolveFileRef: resolveFileRef,
|
||||||
onOpenFile: onOpenFile,
|
onOpenFile: onOpenFile,
|
||||||
mono: ClideSettings.fonts.monoOf(context),
|
mono: ClideSettings.fonts.monoOf(context),
|
||||||
|
ui: ClideSettings.fonts.uiOf(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);
|
||||||
@@ -154,7 +168,7 @@ class ClideMarkdown extends StatelessWidget {
|
|||||||
Text.rich(
|
Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: clideUiFamily,
|
fontFamily: hooks.ui,
|
||||||
fontFamilyFallback: clideUiFamilyFallback,
|
fontFamilyFallback: clideUiFamilyFallback,
|
||||||
fontWeight: clideUiDefaultWeight,
|
fontWeight: clideUiDefaultWeight,
|
||||||
color: tokens.globalForeground,
|
color: tokens.globalForeground,
|
||||||
@@ -344,7 +358,7 @@ class ClideMarkdown extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return TextSpan(
|
return TextSpan(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: clideUiFamily,
|
fontFamily: hooks.ui,
|
||||||
fontFamilyFallback: clideUiFamilyFallback,
|
fontFamilyFallback: clideUiFamilyFallback,
|
||||||
fontWeight: fontWeight ?? clideUiDefaultWeight,
|
fontWeight: fontWeight ?? clideUiDefaultWeight,
|
||||||
color: tokens.globalForeground,
|
color: tokens.globalForeground,
|
||||||
@@ -379,7 +393,7 @@ 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!, hooks.mono, mono: true);
|
if (ref != null) return _fileLinkSpan(raw, ref.$1, ref.$2, tokens, hooks.onOpenFile!, hooks.mono, hooks.ui, mono: true);
|
||||||
}
|
}
|
||||||
return TextSpan(
|
return TextSpan(
|
||||||
text: raw,
|
text: raw,
|
||||||
@@ -401,7 +415,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!, hooks.mono);
|
if (abs != null) return _fileLinkSpan(text, abs, line, tokens, hooks.onOpenFile!, hooks.mono, hooks.ui);
|
||||||
}
|
}
|
||||||
return TextSpan(
|
return TextSpan(
|
||||||
text: text,
|
text: text,
|
||||||
@@ -482,7 +496,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!, hooks.mono)));
|
hits.add(_LinkHit(m.start, m.end, _fileLinkSpan(text.substring(m.start, m.end), abs, line, tokens, hooks.onOpenFile!, hooks.mono, hooks.ui)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hits.isEmpty) return [TextSpan(text: text)];
|
if (hits.isEmpty) return [TextSpan(text: text)];
|
||||||
@@ -560,7 +574,8 @@ class ClideMarkdown extends StatelessWidget {
|
|||||||
int? line,
|
int? line,
|
||||||
SurfaceTokens tokens,
|
SurfaceTokens tokens,
|
||||||
FileTapCallback onOpenFile,
|
FileTapCallback onOpenFile,
|
||||||
String monoFamily, {
|
String monoFamily,
|
||||||
|
String uiFamily, {
|
||||||
bool mono = false,
|
bool mono = false,
|
||||||
}) {
|
}) {
|
||||||
return WidgetSpan(
|
return WidgetSpan(
|
||||||
@@ -575,7 +590,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 ? monoFamily : clideUiFamily,
|
fontFamily: mono ? monoFamily : uiFamily,
|
||||||
fontFamilyFallback: mono ? null : clideUiFamilyFallback,
|
fontFamilyFallback: mono ? null : clideUiFamilyFallback,
|
||||||
decoration: hovered ? TextDecoration.underline : null,
|
decoration: hovered ? TextDecoration.underline : null,
|
||||||
decorationColor: tokens.globalFocus,
|
decorationColor: tokens.globalFocus,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:clide/widgets/widgets.dart';
|
import 'package:clide/widgets/widgets.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import '../../helpers/kernel_fixture.dart';
|
import '../../helpers/kernel_fixture.dart';
|
||||||
@@ -71,4 +72,29 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(find.textContaining('[image: http://x/y.png]'), findsOneWidget);
|
expect(find.textContaining('[image: http://x/y.png]'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// T-475 (UI) / T-472 (mono): the static span builders take no BuildContext,
|
||||||
|
// so they used to pin the bundled const families and ignore the live font
|
||||||
|
// settings. They now read the resolved families from the ClideSettingsScope.
|
||||||
|
testWidgets('prose and inline code honour the live UI + mono families from the scope', (tester) async {
|
||||||
|
await tester.pumpWidget(harness(f, const ClideSettingsScope(ui: 'TestUiFace', mono: 'TestMonoFace', child: ClideMarkdown('hello `snippet` world'))));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
final families = <String?>{};
|
||||||
|
void collect(InlineSpan span) {
|
||||||
|
if (span is TextSpan) {
|
||||||
|
families.add(span.style?.fontFamily);
|
||||||
|
for (final child in span.children ?? const <InlineSpan>[]) {
|
||||||
|
collect(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final t in tester.widgetList<Text>(find.byType(Text))) {
|
||||||
|
final span = t.textSpan;
|
||||||
|
if (span != null) collect(span);
|
||||||
|
}
|
||||||
|
expect(families, contains('TestUiFace')); // prose paragraph
|
||||||
|
expect(families, contains('TestMonoFace')); // inline `code`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user