diff --git a/src/ClaudeCodeVS/Ui/ClaudeToolWindowControl.cs b/src/ClaudeCodeVS/Ui/ClaudeToolWindowControl.cs
index de69730..aef43a7 100644
--- a/src/ClaudeCodeVS/Ui/ClaudeToolWindowControl.cs
+++ b/src/ClaudeCodeVS/Ui/ClaudeToolWindowControl.cs
@@ -69,39 +69,40 @@ 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++)
root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); // feed
- // ---- Row 0: header (title + status pill) ----
+ // ---- Row 0: header (status pill) ----
+ // No "Claude Code" text here: the tool window's own Caption (ClaudeToolWindow.cs) already shows
+ // it in the tab/title bar, so a second copy here just duplicated the first line.
var header = new StackPanel { Margin = new Thickness(0, 0, 0, 8) };
- header.Children.Add(new TextBlock { Text = "Claude Code", FontSize = 15, FontWeight = FontWeights.SemiBold });
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);
// ---- Row 1: toolbar ----
- var toolbar = new DockPanel { Margin = new Thickness(0, 0, 0, 8) };
- var right = new StackPanel { Orientation = Orientation.Horizontal };
- DockPanel.SetDock(right, Dock.Right);
- right.Children.Add(MakeButton(Strings.BtnClear, () => _feed!.Items.Clear()));
- right.Children.Add(MakeButton(Strings.BtnOutput, () => { try { BridgeStatus.ShowOutputAction?.Invoke(); } catch { } }));
-
- var left = new StackPanel { Orientation = Orientation.Horizontal };
- left.Children.Add(MakeButton(Strings.BtnLaunch, () => { _ = BridgeStatus.LaunchAction?.Invoke(); }));
+ // One flat WrapPanel for all four buttons (no DockPanel right-docking): docking Clear/Output to
+ // the right pre-claims width, which clipped the Launch buttons mid-text at narrow panel widths.
+ // Wrapping keeps every button whole and reachable at any width; right-alignment wasn't worth that.
+ var toolbar = new WrapPanel { Margin = new Thickness(0) };
+ toolbar.Children.Add(MakeButton(Strings.BtnLaunch, () => { _ = BridgeStatus.LaunchAction?.Invoke(); }));
var launchExternal = MakeButton(Strings.BtnExternalConsole, () => { _ = BridgeStatus.LaunchExternalAction?.Invoke(); });
launchExternal.ToolTip = Strings.TipExternalConsole;
- left.Children.Add(launchExternal);
+ toolbar.Children.Add(launchExternal);
+ toolbar.Children.Add(MakeButton(Strings.BtnClear, () => _feed!.Items.Clear()));
+ toolbar.Children.Add(MakeButton(Strings.BtnOutput, () => { try { BridgeStatus.ShowOutputAction?.Invoke(); } catch { } }));
// The toggles get their own WRAPPING row: four checkboxes stopped fitting beside the buttons at
// a typically-docked panel width (they were simply invisible until the panel was widened), and a
@@ -165,11 +166,7 @@ public ClaudeToolWindowControl()
_notify.SetResourceReference(ForegroundProperty, VsBrushes.ToolWindowTextKey);
toggles.Children.Add(_notify);
- toolbar.Children.Add(right);
- toolbar.Children.Add(left);
-
var toolbarStack = new StackPanel { Margin = new Thickness(0, 0, 0, 8) };
- toolbar.Margin = new Thickness(0);
toolbarStack.Children.Add(toolbar);
toolbarStack.Children.Add(toggles);
Grid.SetRow(toolbarStack, 1);
@@ -178,16 +175,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 = Strings.BannerConfigTitle,
- 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(Strings.BtnRelaunch, () => { _ = BridgeStatus.RelaunchAction?.Invoke(); }));
@@ -206,14 +202,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(Strings.BtnShowCost, 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 +231,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 +247,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 = Strings.AttachHint,
- 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(Strings.BtnPaste, PasteFromClipboard);
@@ -272,14 +267,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 +299,7 @@ public ClaudeToolWindowControl()
CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, (s, e) => PasteFromClipboard()));
// ---- Row 6: feed label ----
- var feedLabel = new TextBlock { Text = Strings.FeedLabel, FontSize = 10, FontWeight = FontWeights.SemiBold, Opacity = 0.55, Margin = new Thickness(2, 0, 0, 4) };
+ var feedLabel = Font(new TextBlock { Text = Strings.FeedLabel, 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 +380,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);
@@ -670,16 +664,15 @@ private void RefreshAttachments()
var it = item; // capture per chip
var est = it.EstTokens is long e ? "\n" + string.Format(Strings.ChipEstimateFormat, Tok(e)) : "";
var toolNote = it.NeedsTool ? "\n" + Strings.ChipNeedsTool : "";
- var name = new TextBlock
+ var name = Font(new TextBlock
{
Text = (it.IsImage ? "🖼 " : it.NeedsTool ? "🧰 " : "📄 ") + it.FileName + it.RangeShort + (it.Sent ? "" : " ⏳"),
- FontSize = 11.5,
VerticalAlignment = VerticalAlignment.Center,
Cursor = Cursors.Hand,
ToolTip = it.MentionPath + it.RangeLabel + est + toolNote + "\n" + (it.Sent
? Strings.ChipClickRemention
: Strings.ChipStagedRetry),
- };
+ }, 0.96);
name.SetResourceReference(ForegroundProperty, VsBrushes.ToolWindowTextKey);
name.MouseLeftButtonUp += (s, e) => _ = Task.Run(async () =>
{
@@ -687,16 +680,15 @@ private void RefreshAttachments()
FocusClaude(); // chip click = explicit re-send intent
});
- 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 ? Strings.ChipRemoveCopied : Strings.ChipRemove,
- };
+ }, 0.96);
close.SetResourceReference(ForegroundProperty, VsBrushes.ToolWindowTextKey);
close.MouseLeftButtonUp += (s, e) => Attachments.AttachmentService.Remove(it);
@@ -741,6 +733,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()
{
@@ -757,16 +753,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 7b7a58d..b165895 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 });
@@ -68,7 +69,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();
+ }
+}