add ClideMarkdown, ClideCodeBlock, ClideSvgView primitives

Three reusable rendering widgets in lib/widgets/:

- ClideMarkdown: walks markdown AST (GFM tables, task lists,
  fenced code), renders headings, lists, blockquotes, tables,
  inline bold/italic/code/links. Delegates code blocks to
  ClideCodeBlock.

- ClideCodeBlock: syntax-highlighted code via TreeSitterService.
  Async highlight, byte-to-char offset mapping, theme-aware
  colors from SurfaceTokens.

- ClideSvgView: wraps jovial_svg with asset/string constructors
  and optional sizing.

Retrofitted: decision detail body renders as markdown instead of
raw text. Markdown viewer uses ClideMarkdown. Welcome logo uses
ClideSvgView.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 17:36:40 +02:00
co-authored by Claude Opus 4.6
parent 5fd31247da
commit 227d787bc6
7 changed files with 406 additions and 23 deletions
@@ -108,15 +108,7 @@ class _DecisionDetailViewState extends State<DecisionDetailView> {
),
if (body != null && body.isNotEmpty) ...[
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: tokens.panelBackground,
borderRadius: BorderRadius.circular(4),
border: Border.all(color: tokens.panelBorder),
),
child: ClideText(body, fontSize: 13, fontFamily: clideMonoFamily),
),
ClideMarkdown(body),
],
if (refs.isNotEmpty) ...[
const SizedBox(height: 16),
@@ -78,16 +78,12 @@ class _MarkdownViewerState extends State<MarkdownViewer> {
child: ClideText('Open a .md file to preview it here.', muted: true),
);
}
final tokens = ClideTheme.of(context).surface;
return ClidePaneChrome(
title: _path ?? 'viewer',
subtitle: '${_content!.split('\n').length} lines',
child: SingleChildScrollView(
padding: const EdgeInsets.all(12),
child: Text(
_content!,
style: TextStyle(color: tokens.globalForeground, fontSize: 13, fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback),
),
child: ClideMarkdown(_content!),
),
);
}
+1 -9
View File
@@ -2,9 +2,7 @@ import 'dart:async';
import 'package:clide/kernel/kernel.dart';
import 'package:clide/widgets/widgets.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter/widgets.dart';
import 'package:jovial_svg/jovial_svg.dart';
class WelcomeView extends StatelessWidget {
const WelcomeView({super.key});
@@ -56,13 +54,7 @@ class _Header extends StatelessWidget {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 144,
height: 144,
child: ScalableImageWidget.fromSISource(
si: ScalableImageSource.fromSvg(rootBundle, 'assets/logo/logo.svg'),
),
),
const ClideSvgView.asset('assets/logo/logo.svg', width: 144, height: 144),
const SizedBox(width: 24),
Column(
crossAxisAlignment: CrossAxisAlignment.start,