feat(icon): Phosphor glyph card — hero + real-UI-size strip (T-313)
The icon card renders each glyph at a hero size plus a continuous sample strip (10–48px) so legibility is judged at the sizes the app uses, with the entry's optional label + description and a per-entry or card-level color. Wired end to end: icon.show publishes on the `icon` bus, the Claude extension injects an IconMessage, and conversation_view paints the card (display-only, D-78). en+nl catalogs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,10 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- **Phosphor glyph cards.** `clide icon show gear folder` (or `--file` entries
|
||||||
|
with label/description/color) renders glyphs in the conversation at a hero
|
||||||
|
size plus a real-UI-size strip (10–48), for previewing and comparing icons.
|
||||||
|
Resolves by name or 0xNNNN codepoint; honest error on an unknown glyph. (T-313)
|
||||||
- **Annotated image cards.** `clide image show --file meta.json` attaches a
|
- **Annotated image cards.** `clide image show --file meta.json` attaches a
|
||||||
title/label and a longer description to an image card (alongside the existing
|
title/label and a longer description to an image card (alongside the existing
|
||||||
one-line caption); the bare `image show <path> --caption` form is unchanged.
|
one-line caption); the bare `image show <path> --caption` form is unchanged.
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
"conversation.label.thinking": { "translation": "thinking" },
|
"conversation.label.thinking": { "translation": "thinking" },
|
||||||
"conversation.label.agentThinking": { "translation": "agent thinking" },
|
"conversation.label.agentThinking": { "translation": "agent thinking" },
|
||||||
"conversation.label.image": { "translation": "image" },
|
"conversation.label.image": { "translation": "image" },
|
||||||
|
"conversation.label.icon": { "translation": "icons" },
|
||||||
"conversation.label.drawing": { "translation": "drawing" },
|
"conversation.label.drawing": { "translation": "drawing" },
|
||||||
"conversation.draw.viewSource": { "translation": "view d2 source" },
|
"conversation.draw.viewSource": { "translation": "view d2 source" },
|
||||||
"conversation.label.agentRun": { "translation": "agent run" },
|
"conversation.label.agentRun": { "translation": "agent run" },
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
"conversation.label.thinking": { "translation": "nadenken" },
|
"conversation.label.thinking": { "translation": "nadenken" },
|
||||||
"conversation.label.agentThinking": { "translation": "agent denkt na" },
|
"conversation.label.agentThinking": { "translation": "agent denkt na" },
|
||||||
"conversation.label.image": { "translation": "afbeelding" },
|
"conversation.label.image": { "translation": "afbeelding" },
|
||||||
|
"conversation.label.icon": { "translation": "iconen" },
|
||||||
"conversation.label.drawing": { "translation": "tekening" },
|
"conversation.label.drawing": { "translation": "tekening" },
|
||||||
"conversation.draw.viewSource": { "translation": "d2-bron tonen" },
|
"conversation.draw.viewSource": { "translation": "d2-bron tonen" },
|
||||||
"conversation.label.agentRun": { "translation": "agent-uitvoering" },
|
"conversation.label.agentRun": { "translation": "agent-uitvoering" },
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ bool _isFoldable(ConversationItem item, FoldLevel level, Map<String, String> too
|
|||||||
case AssistantTextMessage():
|
case AssistantTextMessage():
|
||||||
case ImageMessage():
|
case ImageMessage():
|
||||||
case DrawingMessage():
|
case DrawingMessage():
|
||||||
|
case IconMessage():
|
||||||
return false;
|
return false;
|
||||||
// Thinking folds at L2+, first-class at L1.
|
// Thinking folds at L2+, first-class at L1.
|
||||||
case AssistantThinkingMessage():
|
case AssistantThinkingMessage():
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import 'package:clide/builtin/claude/src/claude_status.dart' show shortModelLabe
|
|||||||
import 'package:clide/builtin/claude/src/conversation_card.dart';
|
import 'package:clide/builtin/claude/src/conversation_card.dart';
|
||||||
import 'package:clide/builtin/claude/src/conversation_controller.dart';
|
import 'package:clide/builtin/claude/src/conversation_controller.dart';
|
||||||
import 'package:clide/builtin/claude/src/file_tail_follower.dart';
|
import 'package:clide/builtin/claude/src/file_tail_follower.dart';
|
||||||
|
import 'package:clide/builtin/claude/src/icon_card.dart';
|
||||||
import 'package:clide/builtin/claude/src/image_thumbnail.dart';
|
import 'package:clide/builtin/claude/src/image_thumbnail.dart';
|
||||||
import 'package:clide/builtin/claude/src/prompt_card.dart';
|
import 'package:clide/builtin/claude/src/prompt_card.dart';
|
||||||
import 'package:clide/builtin/claude/src/transcript_reader.dart';
|
import 'package:clide/builtin/claude/src/transcript_reader.dart';
|
||||||
@@ -705,6 +706,7 @@ class _ConversationTurn extends StatelessWidget {
|
|||||||
ToolResultMessage() => _toolResult(context, i),
|
ToolResultMessage() => _toolResult(context, i),
|
||||||
ImageMessage() => _image(context, i),
|
ImageMessage() => _image(context, i),
|
||||||
DrawingMessage() => _drawing(context, i),
|
DrawingMessage() => _drawing(context, i),
|
||||||
|
IconMessage() => _icon(context, i),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,6 +737,17 @@ class _ConversationTurn extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A driven-in Phosphor glyph card (T-313): each glyph at a hero size plus a
|
||||||
|
/// real-UI-size strip, with optional label/description/color. Display-only
|
||||||
|
/// per D-78 — selection happens in the interaction zone, not on the card.
|
||||||
|
Widget _icon(BuildContext context, IconMessage m) {
|
||||||
|
return ConversationCard(
|
||||||
|
accent: tokens.globalTextMuted,
|
||||||
|
label: ClideSettings.i18n.string(context, 'conversation.label.icon', namespace: 'builtin.claude', placeholder: 'icons'),
|
||||||
|
body: IconGlyphCard(entries: m.entries, defaultColor: m.color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// A driven-in image card (T-249): the image rendered inline, clide-owned
|
/// A driven-in image card (T-249): the image rendered inline, clide-owned
|
||||||
/// (Flutter's [Image.file], no third-party viewer), display-only per D-78.
|
/// (Flutter's [Image.file], no third-party viewer), display-only per D-78.
|
||||||
/// Bounded so a large image scales down to the pane width and never pushes
|
/// Bounded so a large image scales down to the pane width and never pushes
|
||||||
@@ -1319,5 +1332,7 @@ String _summarizeActivity(BuildContext context, ConversationItem item) {
|
|||||||
return '${label('conversation.label.image', 'image')} $path';
|
return '${label('conversation.label.image', 'image')} $path';
|
||||||
case DrawingMessage(label: final cardLabel):
|
case DrawingMessage(label: final cardLabel):
|
||||||
return '${label('conversation.label.drawing', 'drawing')}${cardLabel != null ? ' $cardLabel' : ''}';
|
return '${label('conversation.label.drawing', 'drawing')}${cardLabel != null ? ' $cardLabel' : ''}';
|
||||||
|
case IconMessage(:final entries):
|
||||||
|
return '${label('conversation.label.icon', 'icons')} ${entries.map((e) => e.name).join(', ')}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ import 'package:clide/builtin/claude/src/session_index.dart';
|
|||||||
import 'package:clide/builtin/claude/src/stream_json_session.dart' show kEffortLevels, kFallbackModels, kPermissionModes;
|
import 'package:clide/builtin/claude/src/stream_json_session.dart' show kEffortLevels, kFallbackModels, kPermissionModes;
|
||||||
import 'package:clide/builtin/claude/src/session_storage.dart';
|
import 'package:clide/builtin/claude/src/session_storage.dart';
|
||||||
import 'package:clide/builtin/claude/src/ticket_pick_up.dart';
|
import 'package:clide/builtin/claude/src/ticket_pick_up.dart';
|
||||||
import 'package:clide/builtin/claude/src/transcript_reader.dart' show DrawingMessage, ImageMessage;
|
import 'package:clide/builtin/claude/src/transcript_reader.dart' show DrawingMessage, IconEntry, IconMessage, ImageMessage;
|
||||||
import 'package:clide/src/daemon/claude_account_commands.dart' show accountActionChannel;
|
import 'package:clide/src/daemon/claude_account_commands.dart' show accountActionChannel;
|
||||||
import 'package:clide/src/daemon/project_commands.dart' show projectCreatedChannel;
|
import 'package:clide/src/daemon/project_commands.dart' show projectCreatedChannel;
|
||||||
import 'package:clide/src/daemon/draw_commands.dart' show drawShowChannel;
|
import 'package:clide/src/daemon/draw_commands.dart' show drawShowChannel;
|
||||||
|
import 'package:clide/src/daemon/icon_commands.dart' show iconShowChannel;
|
||||||
import 'package:clide/src/daemon/image_commands.dart' show imageShowChannel;
|
import 'package:clide/src/daemon/image_commands.dart' show imageShowChannel;
|
||||||
import 'package:clide/builtin/claude/src/team_chat_sidebar.dart' show TeamChatPane;
|
import 'package:clide/builtin/claude/src/team_chat_sidebar.dart' show TeamChatPane;
|
||||||
import 'package:clide/builtin/claude/src/team_panel_host.dart';
|
import 'package:clide/builtin/claude/src/team_panel_host.dart';
|
||||||
@@ -530,6 +531,10 @@ class ClaudeExtension extends ClideExtension {
|
|||||||
// conversation the user is looking at.
|
// conversation the user is looking at.
|
||||||
_subs.add(ctx.messages.subscribe(channel: drawShowChannel).listen(_onDrawShow));
|
_subs.add(ctx.messages.subscribe(channel: drawShowChannel).listen(_onDrawShow));
|
||||||
|
|
||||||
|
// `clide icon show <name…>` (T-313): the dispatcher resolves the glyphs and
|
||||||
|
// publishes an 'icon' message; we inject the glyph card.
|
||||||
|
_subs.add(ctx.messages.subscribe(channel: iconShowChannel).listen(_onIconShow));
|
||||||
|
|
||||||
// A sidebar "pick up" click (T-327) publishes the full ticket; inject it
|
// A sidebar "pick up" click (T-327) publishes the full ticket; inject it
|
||||||
// into the active conversation as a user turn so Claude starts working it.
|
// into the active conversation as a user turn so Claude starts working it.
|
||||||
_subs.add(ctx.messages.subscribe(publisher: 'builtin.tickets', channel: 'pick-up').listen(_onTicketPickUp));
|
_subs.add(ctx.messages.subscribe(publisher: 'builtin.tickets', channel: 'pick-up').listen(_onTicketPickUp));
|
||||||
@@ -647,6 +652,39 @@ class ClaudeExtension extends ClideExtension {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inject an [IconMessage] from a published `icon` bus message (T-313).
|
||||||
|
void _onIconShow(Message m) {
|
||||||
|
final raw = m.data['entries'];
|
||||||
|
if (raw is! List || raw.isEmpty) return;
|
||||||
|
final entries = <IconEntry>[];
|
||||||
|
for (final item in raw) {
|
||||||
|
if (item is! Map) continue;
|
||||||
|
final cp = item['codepoint'];
|
||||||
|
if (cp is! int) continue;
|
||||||
|
entries.add(
|
||||||
|
IconEntry(
|
||||||
|
codepoint: cp,
|
||||||
|
name: item['name'] as String? ?? '',
|
||||||
|
label: item['label'] as String?,
|
||||||
|
description: item['description'] as String?,
|
||||||
|
color: item['color'] as String?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (entries.isEmpty) return;
|
||||||
|
final target = _orchestrator?.byId('primary') ?? _orchestrator?.visibleSessions.firstOrNull;
|
||||||
|
if (target == null) return;
|
||||||
|
target.conversation.inject(
|
||||||
|
IconMessage(
|
||||||
|
uuid: 'icon-${DateTime.now().microsecondsSinceEpoch}',
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
isSidechain: false,
|
||||||
|
entries: entries,
|
||||||
|
color: m.data['color'] as String?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Inject a [DrawingMessage] from a published `draw` bus message (T-318).
|
/// Inject a [DrawingMessage] from a published `draw` bus message (T-318).
|
||||||
/// Dropped silently if no live conversation is available — the CLI already
|
/// Dropped silently if no live conversation is available — the CLI already
|
||||||
/// reported success at publish time, and a missing pane is transient.
|
/// reported success at publish time, and a missing pane is transient.
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/// The Phosphor glyph card (T-313) — display-only per D-78.
|
||||||
|
///
|
||||||
|
/// Each entry shows a HERO glyph (legible detail) plus a continuous sample strip
|
||||||
|
/// at the real UI sizes (10–48), so a reviewer judges how the glyph reads where
|
||||||
|
/// the app actually uses it; the optional per-entry label + description turn the
|
||||||
|
/// card into a labelled offer the interaction zone can mirror as a choice list.
|
||||||
|
/// A per-entry or card-level `color` (hex or CSS name) tints the glyph — content
|
||||||
|
/// color, not a clide token (the glyph is for whatever project we're on); it
|
||||||
|
/// falls back to the card foreground.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:clide/builtin/claude/src/transcript_reader.dart' show IconEntry;
|
||||||
|
import 'package:clide/kernel/src/theme/tokens.dart';
|
||||||
|
import 'package:clide/src/svg/svg_color.dart' show parseSvgColor;
|
||||||
|
import 'package:clide/widgets/widgets.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class IconGlyphCard extends StatelessWidget {
|
||||||
|
const IconGlyphCard({super.key, required this.entries, this.defaultColor});
|
||||||
|
|
||||||
|
final List<IconEntry> entries;
|
||||||
|
|
||||||
|
/// Card-level default glyph color (hex / CSS name), applied to entries without
|
||||||
|
/// their own.
|
||||||
|
final String? defaultColor;
|
||||||
|
|
||||||
|
/// One continuous sample strip, smallest → largest (T-313, finalized set).
|
||||||
|
static const _sizes = <double>[10, 11, 12, 13, 14, 15, 18, 20, 24, 32, 48];
|
||||||
|
static const _hero = 52.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final tokens = ClideSettings.theme.of(context).surface;
|
||||||
|
final cardColor = _parse(defaultColor) ?? tokens.globalForeground;
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
for (var i = 0; i < entries.length; i++) ...[if (i > 0) const SizedBox(height: 18), _entry(tokens, entries[i], cardColor)],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _entry(SurfaceTokens tokens, IconEntry e, Color cardColor) {
|
||||||
|
final color = _parse(e.color) ?? cardColor;
|
||||||
|
final painter = PhosphorIconPainter(e.codepoint);
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (e.label != null && e.label!.isNotEmpty) ClideText(e.label!, fontSize: clideFontMeta, fontWeight: FontWeight.w600, color: tokens.globalForeground),
|
||||||
|
if (e.description != null && e.description!.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 2),
|
||||||
|
child: ClideText(e.description!, fontSize: clideFontCaption, color: tokens.globalTextMuted),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
ClideIcon(painter, size: _hero, color: color),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 14,
|
||||||
|
runSpacing: 8,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.end,
|
||||||
|
children: [for (final s in _sizes) _sample(tokens, painter, color, s)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _sample(SurfaceTokens tokens, PhosphorIconPainter painter, Color color, double size) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ClideIcon(painter, size: size, color: color),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
ClideText('${size.toInt()}', fontSize: clideFontBadge, color: tokens.globalTextMuted),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
Color? _parse(String? raw) {
|
||||||
|
if (raw == null) return null;
|
||||||
|
final argb = parseSvgColor(raw);
|
||||||
|
return argb == null ? null : Color(argb);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -232,6 +232,31 @@ final class DrawingMessage extends ConversationItem {
|
|||||||
String toString() => 'DrawingMessage(${label ?? '<svg>'})';
|
String toString() => 'DrawingMessage(${label ?? '<svg>'})';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One glyph entry on an [IconMessage] (T-313): a resolved Phosphor [codepoint]
|
||||||
|
/// (its [name] kept for copy/debug), with optional per-entry [label],
|
||||||
|
/// [description], and [color] (hex or CSS name, parsed at render).
|
||||||
|
final class IconEntry {
|
||||||
|
const IconEntry({required this.codepoint, required this.name, this.label, this.description, this.color});
|
||||||
|
|
||||||
|
final int codepoint;
|
||||||
|
final String name;
|
||||||
|
final String? label, description, color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A locally-injected Phosphor glyph card (T-313). Driven by `clide icon show`
|
||||||
|
/// (D-6 parity), display-only per D-78. Renders each [entries] glyph at a hero
|
||||||
|
/// size plus a sample strip of real UI sizes, with its optional label +
|
||||||
|
/// description; [color] is the card-level default glyph color.
|
||||||
|
final class IconMessage extends ConversationItem {
|
||||||
|
const IconMessage({required super.uuid, required super.timestamp, required super.isSidechain, required this.entries, this.color});
|
||||||
|
|
||||||
|
final List<IconEntry> entries;
|
||||||
|
final String? color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'IconMessage(${entries.length} glyph${entries.length == 1 ? '' : 's'})';
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Internal helpers
|
// Internal helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import 'package:clide/src/draw/d2_template.dart' show d2TemplateHandler;
|
|||||||
import 'package:clide/src/daemon/editor_commands.dart';
|
import 'package:clide/src/daemon/editor_commands.dart';
|
||||||
import 'package:clide/src/daemon/files_commands.dart';
|
import 'package:clide/src/daemon/files_commands.dart';
|
||||||
import 'package:clide/src/daemon/git_commands.dart';
|
import 'package:clide/src/daemon/git_commands.dart';
|
||||||
|
import 'package:clide/src/daemon/icon_commands.dart';
|
||||||
import 'package:clide/src/daemon/image_commands.dart';
|
import 'package:clide/src/daemon/image_commands.dart';
|
||||||
import 'package:clide/src/daemon/project_commands.dart';
|
import 'package:clide/src/daemon/project_commands.dart';
|
||||||
import 'package:clide/src/daemon/instance_command.dart';
|
import 'package:clide/src/daemon/instance_command.dart';
|
||||||
@@ -61,6 +62,7 @@ import 'package:clide/src/git/client.dart';
|
|||||||
import 'package:clide/src/cli/argv_dispatch.dart';
|
import 'package:clide/src/cli/argv_dispatch.dart';
|
||||||
import 'package:clide/src/env/shell_env.dart' show primeLoginShellPath;
|
import 'package:clide/src/env/shell_env.dart' show primeLoginShellPath;
|
||||||
import 'package:clide/src/env/supporter_binaries.dart';
|
import 'package:clide/src/env/supporter_binaries.dart';
|
||||||
|
import 'package:clide/widgets/src/icons/phosphor_glyphs.g.dart' show kPhosphorGlyphs;
|
||||||
import 'package:clide/src/ipc/envelope.dart';
|
import 'package:clide/src/ipc/envelope.dart';
|
||||||
import 'package:clide/src/ipc/mcp_server.dart';
|
import 'package:clide/src/ipc/mcp_server.dart';
|
||||||
import 'package:clide/src/ipc/paths.dart' show workspaceSocketPath, logDirectory;
|
import 'package:clide/src/ipc/paths.dart' show workspaceSocketPath, logDirectory;
|
||||||
@@ -377,6 +379,21 @@ Future<void> main() async {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
// `clide icon show <name…>` — drive a Phosphor glyph card into the Claude
|
||||||
|
// conversation (T-313). Resolves names via the bundled glyph table.
|
||||||
|
registerIconCommands(
|
||||||
|
dispatcher,
|
||||||
|
() => kernelMessages?.publish,
|
||||||
|
resolve: (name) => kPhosphorGlyphs[name],
|
||||||
|
readFile: (path) async {
|
||||||
|
final file = File(path.startsWith('/') ? path : '${workRoot.path}/$path');
|
||||||
|
try {
|
||||||
|
return file.existsSync() ? await file.readAsString() : null;
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
// `clide draw --file <doc>` — drive a drawing card into the Claude
|
// `clide draw --file <doc>` — drive a drawing card into the Claude
|
||||||
// conversation (T-318, drive-half of D-6). Reads the JSON doc relative to
|
// conversation (T-318, drive-half of D-6). Reads the JSON doc relative to
|
||||||
// workRoot, lowers it to SVG via the template registry (primitive svg now;
|
// workRoot, lowers it to SVG via the template registry (primitive svg now;
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ ToolResultMessage _result(String content, {bool isError = false}) =>
|
|||||||
ImageMessage _image(String path, {String? caption, String? label, String? description}) =>
|
ImageMessage _image(String path, {String? caption, String? label, String? description}) =>
|
||||||
ImageMessage(uuid: 'i', timestamp: _t, isSidechain: false, path: path, caption: caption, label: label, description: description);
|
ImageMessage(uuid: 'i', timestamp: _t, isSidechain: false, path: path, caption: caption, label: label, description: description);
|
||||||
|
|
||||||
|
IconMessage _iconMsg(List<IconEntry> entries, {String? color}) => IconMessage(uuid: 'ic', timestamp: _t, isSidechain: false, entries: entries, color: color);
|
||||||
|
|
||||||
class _MockClipboard {
|
class _MockClipboard {
|
||||||
Map<String, dynamic> _data = {'text': null};
|
Map<String, dynamic> _data = {'text': null};
|
||||||
Future<Object?> handleMethodCall(MethodCall call) async {
|
Future<Object?> handleMethodCall(MethodCall call) async {
|
||||||
@@ -480,6 +482,17 @@ void main() {
|
|||||||
expect(find.text('before'), findsOneWidget);
|
expect(find.text('before'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('an icon card renders the glyph label, description, and size strip (T-313)', (tester) async {
|
||||||
|
await pumpWith(tester, [
|
||||||
|
_iconMsg([const IconEntry(codepoint: 0xe2a4, name: 'gear', label: 'Settings', description: 'global scope')]),
|
||||||
|
]);
|
||||||
|
expect(find.text('icons'), findsOneWidget); // card label
|
||||||
|
expect(find.text('Settings'), findsOneWidget); // entry label
|
||||||
|
expect(find.text('global scope'), findsOneWidget);
|
||||||
|
expect(find.text('10'), findsOneWidget); // smallest strip sample
|
||||||
|
expect(find.text('48'), findsOneWidget); // largest strip sample
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('inject() drives a new image card into a live view (T-249)', (tester) async {
|
testWidgets('inject() drives a new image card into a live view (T-249)', (tester) async {
|
||||||
final c = await pumpWith(tester, [_user('hi')]);
|
final c = await pumpWith(tester, [_user('hi')]);
|
||||||
expect(find.text('image'), findsNothing);
|
expect(find.text('image'), findsNothing);
|
||||||
|
|||||||
Reference in New Issue
Block a user