diff --git a/src/ClaudeCodeVS/Ui/ClaudeToolWindowControl.cs b/src/ClaudeCodeVS/Ui/ClaudeToolWindowControl.cs
index 5d3be96..546f466 100644
--- a/src/ClaudeCodeVS/Ui/ClaudeToolWindowControl.cs
+++ b/src/ClaudeCodeVS/Ui/ClaudeToolWindowControl.cs
@@ -69,6 +69,7 @@ public ClaudeToolWindowControl()
SetResourceReference(BackgroundProperty, VsBrushes.ToolWindowBackgroundKey);
SetResourceReference(ForegroundProperty, VsBrushes.ToolWindowTextKey);
SetResourceReference(TextBlock.ForegroundProperty, VsBrushes.ToolWindowTextKey);
+ FontScale.BindRoot(this);
var root = new Grid { Margin = new Thickness(10, 8, 10, 8) };
for (int i = 0; i < 7; i++)
@@ -77,16 +78,16 @@ public ClaudeToolWindowControl()
// ---- Row 0: header (title + status pill) ----
var header = new StackPanel { Margin = new Thickness(0, 0, 0, 8) };
- header.Children.Add(new TextBlock { Text = "Claude Code", FontSize = 15, FontWeight = FontWeights.SemiBold });
+ header.Children.Add(Font(new TextBlock { Text = "Claude Code", FontWeight = FontWeights.SemiBold }, 1.25));
var statusRow = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 0) };
_dot = new Ellipse { Width = 9, Height = 9, Fill = DotIdle, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 6, 0) };
- _statusLine = new TextBlock { VerticalAlignment = VerticalAlignment.Center, FontSize = 12 };
+ _statusLine = Font(new TextBlock { VerticalAlignment = VerticalAlignment.Center }, 1.0);
statusRow.Children.Add(_dot);
statusRow.Children.Add(_statusLine);
header.Children.Add(statusRow);
- _endpointLine = new TextBlock { FontSize = 11, Opacity = 0.65, TextTrimming = TextTrimming.CharacterEllipsis, Margin = new Thickness(0, 1, 0, 0) };
+ _endpointLine = Font(new TextBlock { Opacity = 0.65, TextTrimming = TextTrimming.CharacterEllipsis, Margin = new Thickness(0, 1, 0, 0) }, 0.92);
header.Children.Add(_endpointLine);
Grid.SetRow(header, 0);
@@ -178,16 +179,15 @@ public ClaudeToolWindowControl()
// Surfaces the otherwise-silent gap where the IDE WebSocket connected but vs-debug/vs-semantic/
// tests never loaded (Claude launched outside the workspace, or project servers unapproved).
var warnStack = new StackPanel();
- _toolsWarningTitle = new TextBlock
+ _toolsWarningTitle = Font(new TextBlock
{
Text = "ā Workspace hooks & tools didn't load for this session",
- FontSize = 12,
FontWeight = FontWeights.SemiBold,
Foreground = WarnText,
TextWrapping = TextWrapping.Wrap,
- };
+ }, 1.0);
warnStack.Children.Add(_toolsWarningTitle);
- _toolsWarningText = new TextBlock { FontSize = 11.5, Opacity = 0.85, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 3, 0, 0) };
+ _toolsWarningText = Font(new TextBlock { Opacity = 0.85, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 3, 0, 0) }, 0.96);
warnStack.Children.Add(_toolsWarningText);
var warnButtons = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 6, 0, 0) };
warnButtons.Children.Add(MakeButton("Relaunch Claude Code", () => { _ = BridgeStatus.LaunchAction?.Invoke(); }));
@@ -206,14 +206,14 @@ public ClaudeToolWindowControl()
Grid.SetRow(_toolsWarningCard, 2);
// ---- Row 3: stats card ----
- _editsLine = new TextBlock { FontSize = 12, TextWrapping = TextWrapping.Wrap };
- _debugLine = new TextBlock { FontSize = 12, Opacity = 0.9, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 2, 0, 0) };
- _latestLine = new TextBlock { FontSize = 12, Opacity = 0.9, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 4, 0, 0) };
- _sessionLine = new TextBlock { FontSize = 12, Opacity = 0.9, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 2, 0, 0) };
+ _editsLine = Font(new TextBlock { TextWrapping = TextWrapping.Wrap }, 1.0);
+ _debugLine = Font(new TextBlock { Opacity = 0.9, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 2, 0, 0) }, 1.0);
+ _latestLine = Font(new TextBlock { Opacity = 0.9, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 4, 0, 0) }, 1.0);
+ _sessionLine = Font(new TextBlock { Opacity = 0.9, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 2, 0, 0) }, 1.0);
// Cost is an estimate, so it's gated behind a toggle rather than shown by default.
_costButton = MakeButton("ā Show est. cost", ToggleCost);
- _costText = new TextBlock { FontSize = 12, VerticalAlignment = VerticalAlignment.Center, Opacity = 0.9, Margin = new Thickness(8, 0, 0, 0), Visibility = Visibility.Collapsed };
+ _costText = Font(new TextBlock { VerticalAlignment = VerticalAlignment.Center, Opacity = 0.9, Margin = new Thickness(8, 0, 0, 0), Visibility = Visibility.Collapsed }, 1.0);
_costRow = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 6, 0, 0), Visibility = Visibility.Collapsed };
_costRow.Children.Add(_costButton);
_costRow.Children.Add(_costText);
@@ -235,7 +235,7 @@ public ClaudeToolWindowControl()
Grid.SetRow(statsCard, 3);
// ---- Row 4: pending diffs (collapsed when none) ----
- _pendingText = new TextBlock { FontSize = 12, TextWrapping = TextWrapping.Wrap };
+ _pendingText = Font(new TextBlock { TextWrapping = TextWrapping.Wrap }, 1.0);
_pendingCard = new Border
{
Background = Chip,
@@ -251,13 +251,12 @@ public ClaudeToolWindowControl()
// Screenshots can't be pasted into the CLI on Windows (open upstream gap), so the panel is the
// paste/drop point: stage the file, then push an at_mentioned so the reference lands in the
// CLI's composer with no path typing. Chips show what's staged; Ć removes, click re-mentions.
- var attachHint = new TextBlock
+ var attachHint = Font(new TextBlock
{
Text = "š Attach ā drop images/files here, or",
- FontSize = 11.5,
Opacity = 0.75,
VerticalAlignment = VerticalAlignment.Center,
- };
+ }, 0.96);
var attachHeader = new StackPanel { Orientation = Orientation.Horizontal };
attachHeader.Children.Add(attachHint);
var pasteBtn = MakeButton("Paste", PasteFromClipboard);
@@ -272,14 +271,13 @@ public ClaudeToolWindowControl()
_attachClear.Visibility = Visibility.Collapsed;
attachHeader.Children.Add(_attachClear);
// A live "what will this cost" readout for the staged set - same estimate language as the cost row.
- _attachSummary = new TextBlock
+ _attachSummary = Font(new TextBlock
{
- FontSize = 11.5,
Opacity = 0.65,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(2, 0, 0, 0),
Visibility = Visibility.Collapsed,
- };
+ }, 0.96);
_attachSummary.SetResourceReference(ForegroundProperty, VsBrushes.ToolWindowTextKey);
attachHeader.Children.Add(_attachSummary);
@@ -305,7 +303,7 @@ public ClaudeToolWindowControl()
CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, (s, e) => PasteFromClipboard()));
// ---- Row 6: feed label ----
- var feedLabel = new TextBlock { Text = "ACTIVITY", FontSize = 10, FontWeight = FontWeights.SemiBold, Opacity = 0.55, Margin = new Thickness(2, 0, 0, 4) };
+ var feedLabel = Font(new TextBlock { Text = "ACTIVITY", FontWeight = FontWeights.SemiBold, Opacity = 0.55, Margin = new Thickness(2, 0, 0, 4) }, 0.83);
Grid.SetRow(feedLabel, 6);
// ---- Row 7: curated activity feed ----
@@ -386,7 +384,7 @@ private void AddFeedLine(LogLevel level, string text)
// Raw JSON frames and notification noise stay in the Output pane; keep the panel readable.
if (level == LogLevel.Frame || level == LogLevel.Event) return;
- var tb = new TextBlock { Text = text, FontFamily = Mono, FontSize = 11.5, TextWrapping = TextWrapping.NoWrap };
+ var tb = Font(new TextBlock { Text = text, FontFamily = Mono, TextWrapping = TextWrapping.NoWrap }, 0.96);
if (level == LogLevel.Error) tb.Foreground = ErrText;
else if (level == LogLevel.Warn) tb.Foreground = WarnText;
else tb.SetResourceReference(ForegroundProperty, VsBrushes.ToolWindowTextKey);
@@ -664,29 +662,27 @@ private void RefreshAttachments()
var it = item; // capture per chip
var est = it.EstTokens is long e ? $"\nā {Tok(e)} tokens when read (estimate)" : "";
var toolNote = it.NeedsTool ? "\nNot a format Claude reads directly - it will use a script/tool on it." : "";
- var name = new TextBlock
+ var name = Font(new TextBlock
{
Text = (it.IsImage ? "š¼ " : it.NeedsTool ? "š§° " : "š ") + it.FileName + (it.Sent ? "" : " ā³"),
- FontSize = 11.5,
VerticalAlignment = VerticalAlignment.Center,
Cursor = Cursors.Hand,
ToolTip = it.MentionPath + est + toolNote + (it.Sent
? "\nClick to @-mention it again."
: "\nStaged - sends when Claude connects. Click to retry."),
- };
+ }, 0.96);
name.SetResourceReference(ForegroundProperty, VsBrushes.ToolWindowTextKey);
name.MouseLeftButtonUp += (s, e) => _ = Task.Run(() => Attachments.AttachmentService.ResendAsync(it));
- var close = new TextBlock
+ var close = Font(new TextBlock
{
Text = "ā",
- FontSize = 11.5,
Margin = new Thickness(7, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
Cursor = Cursors.Hand,
Opacity = 0.65,
ToolTip = it.WasCopied ? "Remove (deletes the staged copy)" : "Remove from the tray",
- };
+ }, 0.96);
close.SetResourceReference(ForegroundProperty, VsBrushes.ToolWindowTextKey);
close.MouseLeftButtonUp += (s, e) => Attachments.AttachmentService.Remove(it);
@@ -731,6 +727,10 @@ private static string ShortModel(string model)
return model;
}
+ /// Scales an element's FontSize relative to VS's Environment Font size (see ).
+ private static T Font(T el, double ratio) where T : FrameworkElement
+ => FontScale.Bind(el, typeof(ClaudeToolWindowControl), ratio);
+
/// A flat, non-selectable list item (so the feed reads like a log, not a selectable list).
private static Style FlatItemStyle()
{
@@ -747,16 +747,15 @@ private static Style FlatItemStyle()
/// A flat, theme-aware button: a rounded chip that lightens on hover, themed text.
private static Button MakeButton(string text, Action onClick)
{
- var b = new Button
+ var b = Font(new Button
{
Content = text,
Margin = new Thickness(0, 0, 6, 0),
Padding = new Thickness(10, 3, 10, 3),
- FontSize = 12,
Cursor = Cursors.Hand,
Background = Chip,
BorderThickness = new Thickness(0),
- };
+ }, 1.0);
b.SetResourceReference(ForegroundProperty, VsBrushes.ToolWindowTextKey);
var border = new System.Windows.FrameworkElementFactory(typeof(Border));
diff --git a/src/ClaudeCodeVS/Ui/ComposeDialog.cs b/src/ClaudeCodeVS/Ui/ComposeDialog.cs
index c0305cd..4ab6a97 100644
--- a/src/ClaudeCodeVS/Ui/ComposeDialog.cs
+++ b/src/ClaudeCodeVS/Ui/ComposeDialog.cs
@@ -33,6 +33,7 @@ private ComposeDialog(string initialText)
SetResourceReference(BackgroundProperty, VsBrushes.ToolWindowBackgroundKey);
SetResourceReference(ForegroundProperty, VsBrushes.ToolWindowTextKey);
+ FontScale.BindRoot(this);
var grid = new Grid { Margin = new Thickness(12) };
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
@@ -69,7 +70,7 @@ private ComposeDialog(string initialText)
Grid.SetRow(_box, 1);
var footer = new DockPanel { Margin = new Thickness(0, 8, 0, 0) };
- _estimate = new TextBlock { VerticalAlignment = VerticalAlignment.Center, Opacity = 0.65, FontSize = 11.5 };
+ _estimate = Font(new TextBlock { VerticalAlignment = VerticalAlignment.Center, Opacity = 0.65 }, 0.96);
_estimate.SetResourceReference(TextBlock.ForegroundProperty, VsBrushes.ToolWindowTextKey);
footer.Children.Add(_estimate);
@@ -115,6 +116,10 @@ private void UpdateEstimate()
return string.IsNullOrWhiteSpace(text) ? null : text;
}
+ /// Scales an element's FontSize relative to VS's Environment Font size (see ).
+ private static T Font(T el, double ratio) where T : FrameworkElement
+ => FontScale.Bind(el, typeof(ComposeDialog), ratio);
+
/// Minimal ICommand for the Ctrl+Enter accelerator (no framework helper in scope).
private sealed class RelayCommand : ICommand
{
diff --git a/src/ClaudeCodeVS/Ui/FontScale.cs b/src/ClaudeCodeVS/Ui/FontScale.cs
new file mode 100644
index 0000000..6ea7cdf
--- /dev/null
+++ b/src/ClaudeCodeVS/Ui/FontScale.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Documents;
+using Microsoft.VisualStudio.Shell;
+
+namespace ClaudeCodeVs.Ui;
+
+///
+/// Ties this extension's hand-built WPF panels to VS's own Environment Font size (Tools > Options >
+/// Environment > Fonts and Colors), instead of the hardcoded point values every element used before.
+/// makes a panel's own FontSize track VsFonts.EnvironmentFontSizeKey;
+/// scales a descendant relative to that root so the panel's size hierarchy
+/// (title vs. body vs. caption) survives, and both update live if the user changes the VS setting.
+///
+internal static class FontScale
+{
+ private static readonly IValueConverter Ratio = new RatioConverter();
+
+ public static void BindRoot(FrameworkElement root)
+ => root.SetResourceReference(TextElement.FontSizeProperty, VsFonts.EnvironmentFontSizeKey);
+
+ public static T Bind(T el, Type rootType, double ratio = 1.0) where T : FrameworkElement
+ {
+ el.SetBinding(TextElement.FontSizeProperty, new Binding
+ {
+ Path = new PropertyPath(TextElement.FontSizeProperty),
+ RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, rootType, 1),
+ Converter = Ratio,
+ ConverterParameter = ratio,
+ });
+ return el;
+ }
+
+ private sealed class RatioConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ => value is double d && parameter is double r ? d * r : value;
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ => throw new NotSupportedException();
+ }
+}