Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fe5d6d3e8 | ||
|
|
8130d6b43a | ||
|
|
90546cb446 |
@@ -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 ('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 ('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 ('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 ('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,21 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
||||
|
||||
### Fixed
|
||||
|
||||
## [2.7.1] — 2026-06-18
|
||||
|
||||
### Changed
|
||||
|
||||
- **Josefin Sans is the default UI font again.** Reverts the Inter default from
|
||||
2.7.0; Inter stays bundled and selectable in Settings → Appearance. JetBrains
|
||||
Mono remains the default monospace face (Fira Mono selectable). (T-460)
|
||||
|
||||
### 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
|
||||
|
||||
### Added
|
||||
|
||||
@@ -39,7 +39,7 @@ self:
|
||||
# Auto-synced from pubspec.yaml `version:` by `make gen-build-info`
|
||||
# (runs implicitly on every build/run/test). Don't hand-edit; bump
|
||||
# pubspec instead.
|
||||
version: "2.7.0"
|
||||
version: "2.7.1"
|
||||
homepage: https://github.com/postmeridiem/clide
|
||||
license: MIT
|
||||
license_file: assets/LICENSE
|
||||
@@ -76,8 +76,8 @@ dependencies:
|
||||
license: OFL-1.1
|
||||
license_file: assets/fonts/inter/OFL.txt
|
||||
purpose: >-
|
||||
Default application UI face (T-460). Variable font with optical-size
|
||||
and weight axes; Josefin Sans remains bundled as a selectable option.
|
||||
Selectable application UI face (bundled T-460); Josefin Sans is the
|
||||
default. Variable font with optical-size and weight axes.
|
||||
weights_bundled: [VariableFont, Italic-VariableFont]
|
||||
|
||||
- name: Josefin Sans
|
||||
@@ -87,8 +87,9 @@ dependencies:
|
||||
license: OFL-1.1
|
||||
license_file: assets/fonts/josefin_sans/OFL.txt
|
||||
purpose: >-
|
||||
Selectable application UI face (was the default before T-460). Default
|
||||
weight Light (300); full 100-700 range via the variable-font weight axis.
|
||||
Default application UI face; default weight Light (300), full 100-700
|
||||
range via the variable-font weight axis. Inter is bundled as a
|
||||
selectable alternative.
|
||||
weights_bundled: [VariableFont, Italic-VariableFont]
|
||||
|
||||
- name: Phosphor Icons
|
||||
|
||||
@@ -64,10 +64,10 @@ class ThemePickerExtension extends ClideExtension {
|
||||
kind: SettingsFieldKind.select,
|
||||
label: 'UI font',
|
||||
help: 'Typeface for the app interface; applies live.',
|
||||
defaultValue: 'Inter',
|
||||
defaultValue: 'JosefinSans',
|
||||
options: [
|
||||
SettingsOption(value: 'Inter', label: 'Inter'),
|
||||
SettingsOption(value: 'JosefinSans', label: 'Josefin Sans'),
|
||||
SettingsOption(value: 'Inter', label: 'Inter'),
|
||||
],
|
||||
),
|
||||
SettingsField(
|
||||
|
||||
@@ -35,7 +35,15 @@ typedef FileTapCallback = void Function(String path, int? line);
|
||||
/// inert (the text renders, just not interactive).
|
||||
@immutable
|
||||
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).
|
||||
final RecordTapCallback? onRecordTap;
|
||||
@@ -59,6 +67,11 @@ class ClideMarkdownHooks {
|
||||
/// even though the span builders are context-free statics (T-472).
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -123,6 +136,7 @@ class ClideMarkdown extends StatelessWidget {
|
||||
resolveFileRef: resolveFileRef,
|
||||
onOpenFile: onOpenFile,
|
||||
mono: ClideSettings.fonts.monoOf(context),
|
||||
ui: ClideSettings.fonts.uiOf(context),
|
||||
);
|
||||
final widgets = _buildNodes(nodes, tokens, hooks);
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: widgets);
|
||||
@@ -154,7 +168,7 @@ class ClideMarkdown extends StatelessWidget {
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
fontFamily: clideUiFamily,
|
||||
fontFamily: hooks.ui,
|
||||
fontFamilyFallback: clideUiFamilyFallback,
|
||||
fontWeight: clideUiDefaultWeight,
|
||||
color: tokens.globalForeground,
|
||||
@@ -344,7 +358,7 @@ class ClideMarkdown extends StatelessWidget {
|
||||
}
|
||||
return TextSpan(
|
||||
style: TextStyle(
|
||||
fontFamily: clideUiFamily,
|
||||
fontFamily: hooks.ui,
|
||||
fontFamilyFallback: clideUiFamilyFallback,
|
||||
fontWeight: fontWeight ?? clideUiDefaultWeight,
|
||||
color: tokens.globalForeground,
|
||||
@@ -379,7 +393,7 @@ class ClideMarkdown extends StatelessWidget {
|
||||
// editor (T-300); other inline code renders verbatim.
|
||||
if (hooks.resolveFileRef != null && hooks.onOpenFile != null) {
|
||||
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(
|
||||
text: raw,
|
||||
@@ -401,7 +415,7 @@ class ClideMarkdown extends StatelessWidget {
|
||||
if (hooks.resolveFileRef != null && hooks.onOpenFile != null && href != null) {
|
||||
final (path, line) = _splitFileRef(href);
|
||||
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(
|
||||
text: text,
|
||||
@@ -482,7 +496,7 @@ class ClideMarkdown extends StatelessWidget {
|
||||
final abs = hooks.resolveFileRef!(m.group(1)!);
|
||||
if (abs == null) continue;
|
||||
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)];
|
||||
@@ -560,7 +574,8 @@ class ClideMarkdown extends StatelessWidget {
|
||||
int? line,
|
||||
SurfaceTokens tokens,
|
||||
FileTapCallback onOpenFile,
|
||||
String monoFamily, {
|
||||
String monoFamily,
|
||||
String uiFamily, {
|
||||
bool mono = false,
|
||||
}) {
|
||||
return WidgetSpan(
|
||||
@@ -575,7 +590,7 @@ class ClideMarkdown extends StatelessWidget {
|
||||
color: tokens.globalFocus,
|
||||
fontSize: mono ? clideFontMono : _fontSize,
|
||||
height: _lineHeight,
|
||||
fontFamily: mono ? monoFamily : clideUiFamily,
|
||||
fontFamily: mono ? monoFamily : uiFamily,
|
||||
fontFamilyFallback: mono ? null : clideUiFamilyFallback,
|
||||
decoration: hovered ? TextDecoration.underline : null,
|
||||
decorationColor: tokens.globalFocus,
|
||||
|
||||
@@ -16,23 +16,23 @@ library;
|
||||
import 'package:flutter/widgets.dart' show FontWeight;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UI face — Inter
|
||||
// UI face — Josefin Sans
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// The bundled application UI family, resolved first (T-460). The settings
|
||||
/// override ([kUiFontSettingKey]) takes precedence at the app root when set.
|
||||
const String clideUiFamily = 'Inter';
|
||||
/// The bundled default application UI family, resolved first. Inter stays
|
||||
/// bundled and selectable (Settings → Appearance); the settings override
|
||||
/// ([kUiFontSettingKey]) takes precedence at the app root when set.
|
||||
const String clideUiFamily = 'JosefinSans';
|
||||
|
||||
/// Default weight for UI text.
|
||||
const FontWeight clideUiDefaultWeight = FontWeight.w400;
|
||||
/// Default weight for UI text — Josefin Sans reads best at Light.
|
||||
const FontWeight clideUiDefaultWeight = FontWeight.w300;
|
||||
|
||||
/// System fallback chain for the UI face — the other bundled UI option plus
|
||||
/// platform humanist sans defaults.
|
||||
const List<String> clideUiFamilyFallback = [
|
||||
// The other bundled UI family (selectable in Appearance).
|
||||
'JosefinSans',
|
||||
// User system install / platform humanist sans defaults.
|
||||
'Inter',
|
||||
// User system install / platform humanist sans defaults.
|
||||
'Helvetica Neue',
|
||||
'Helvetica',
|
||||
'Arial',
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ description: >-
|
||||
subsystem handlers (pane, files, editor, git, pql), and the
|
||||
extension framework.
|
||||
publish_to: none
|
||||
version: 2.7.0
|
||||
version: 2.7.1
|
||||
repository: https://github.com/postmeridiem/clide
|
||||
# Short user-facing tagline (the welcome subtitle, web meta
|
||||
# description, etc.). Baked into lib/src/build_info.g.dart by
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
library;
|
||||
|
||||
import 'package:clide/widgets/widgets.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../helpers/kernel_fixture.dart';
|
||||
@@ -71,4 +72,29 @@ void main() {
|
||||
await tester.pump();
|
||||
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