make-wpf-chat-bubble-template — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited make-wpf-chat-bubble-template (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
If `$0` is empty, use the AskUserQuestion tool to ask: "Enter the chat turn ViewModel name (e.g., ChatTurnViewModel, MessageViewModel)". Do NOT proceed until a valid name is provided. Use the response as the turn ViewModel name for all subsequent steps.
Generate the role-differentiated chat bubble DataTemplate (opposite alignment, distinct background, asymmetric gutter, sized to content) and the $0 turn ViewModel that drives it, for an ItemsControl of chat turns.
{Namespace} with the project's root namespace.MarkdownPresenter (generate it with/wpf-dev-pack:make-wpf-markdown-presenter) so each turn renders selectable markdown.
The governing rule (full rationale in thestyling-chat-bubbles-in-wpfknowledge topic — fetch viaWpfDevPackMcp get_wpf_topic): declare the base (assistant) values as Style Setters and the per-role override as a DataTrigger Setter — NEVER set a trigger-controlled property (Background,HorizontalAlignment,Margin) as an inline attribute on theBorder. In WPF dependency-property value precedence a local value (an inline attribute) outranks a Style trigger Setter, so an inline attribute silently defeats the roleDataTriggerand both roles render identically.
/wpf-dev-pack:make-wpf-chat-bubble-template ChatTurnViewModelIsUser discriminates the role; Markdown is the content the streaming orchestrator appends to. Lives in the ViewModels project (no System.Windows).
Skip this file if `/wpf-dev-pack:make-wpf-chatclient` already emitted a turn ViewModel — emitting both produces a duplicate-type collision. The chatclient's version is this class plus an IsWaiting flag.namespace {Namespace}.ViewModels;
public sealed partial class $0 : ObservableObject
{
[ObservableProperty] private bool _isUser;
[ObservableProperty] private bool _isWaiting; // "waiting…" until the first streamed token
[ObservableProperty] private string _markdown = string.Empty;
// The streaming orchestrator appends tokens here; the bound MarkdownPresenter re-renders.
public void AppendMarkdown(string delta) => Markdown += delta;
}The default uses stock-WPF brushes so it builds with no UI-kit package:
<ItemsControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:{Namespace}.ViewModels;assembly={Namespace}.ViewModels"
xmlns:controls="clr-namespace:{Namespace}.Controls"
ItemsSource="{Binding Turns}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:$0}">
<Border Padding="10" CornerRadius="6">
<Border.Style>
<Style TargetType="Border">
<!-- DEFAULT = ASSISTANT (response): left, neutral background, right gutter -->
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="0,4,48,0"/>
<Setter Property="Background" Value="#FFF0F0F0"/>
<Style.Triggers>
<!-- USER (request): right, tinted background, left gutter -->
<DataTrigger Binding="{Binding IsUser}" Value="True">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="Margin" Value="48,4,0,0"/>
<Setter Property="Background" Value="#FFD6E8FF"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<!-- content hugs (MarkdownPresenter reports a hug desired width);
the 48px gutter caps max width = parent - 48 -->
<controls:MarkdownPresenter Markdown="{Binding Markdown}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>| Rule | Why |
|---|---|
| Base values = Style Setter; role override = DataTrigger Setter | A local (inline) value outranks a Style trigger Setter, so inline attributes defeat the trigger. |
HorizontalAlignment = Left/Right, never Stretch | Stretch forces the bubble to full width; hugging needs Left/Right and a child that reports a hug width. |
One asymmetric Margin per role | Doubles as the "who sent it" gutter and the max-width cap (parent − gutter); no separate MaxWidth needed. |
Properties constant across roles (Padding, CornerRadius) | Safe to leave as inline attributes — never under trigger control. |
WPF-UI variant — if (and only if) the project referencesWpf.Ui, swap the twoBackgroundSetters for theme-aware kit brushes and addxmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml":{ui:ThemeResource CardBackgroundFillColorDefaultBrush}(assistant) /{ui:ThemeResource SystemAccentColorPrimaryBrush}(user). Declaringxmlns:uiwithout the package installed is an MC3074 build error, and an invalid theme-resource key throwsXamlParseExceptionat render time — use only keys verified to exist in the kit.
{Project}.ViewModels/
└── $0.cs
{Project}.WpfApp/
└── Views/ (place the ItemsControl XAML in the chat view)WpfDevPackMcp get_wpf_topic): styling-chat-bubbles-in-wpf,displaying-selectable-rich-text-in-wpf, managing-styles-resourcedictionary, virtualizing-wpf-ui (for long histories of these bubbles).
/wpf-dev-pack:make-wpf-markdown-presenter (the bubble'scontent control). This template is one of the pieces emitted by the one-button /wpf-dev-pack:make-wpf-chatclient.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.