Problem
Rewritten 2026-09-16 against current source. Every number below is re-derived from Views/*.xaml +
MainWindow.xaml with XAML comments stripped, counting <TextBlock> start tags only and excluding
elements that already name a Style or use an icon font (Segoe Fluent / Segoe MDL2, where a FontSize
is a glyph box rather than typography). 934 TextBlocks inspected. The original body counted every
FontSize="…" in the files, including icon glyphs, TextBox/ComboBox and style definitions, which is why
its totals cannot be compared with these.
The number half of this issue is finished. The body-text half is not, and it turns out to be blocked
on a prerequisite that has to be settled first.
What shipped
The scale now has twelve named styles, and all of them are used:
| Token |
Size |
Uses |
Subtle |
12 |
200 |
Display |
28 |
59 |
Body |
13 |
59 |
SectionLabel |
11 SemiBold |
52 |
SectionTitle |
14 |
44 |
Caption |
11 |
19 |
MetricCompact |
16 |
13 |
Metric |
22 |
12 |
MetricLarge |
26 |
8 |
Heading |
20 |
5 |
MetricSmall |
20 |
4 |
MetricHero |
30 |
1 |
Metric rungs at 16 / 20 / 22 / 26 / 30, text at 28 / 20 / 14 / 13 / 12 / 11. Zero bare TextBlocks remain at
13, 18, 26, 28, 30 or 32. ArchitectureTests.EveryMetricRungSize_IsReachedThroughItsRung holds that line
and also asserts every rung a view names is actually defined, which a compile cannot tell you because
{StaticResource} inside a DataTemplate resolves at runtime.
Corrections to the original body
Heading was never dead. It has 5 uses today, and the claim was wrong when filed: the grep behind it
searched for StaticResource Heading where the markup writes {StaticResource Heading}.
Metric did not "match no shipped value". It had 10 uses when filed and has 12 now. It was never
re-pointed; instead the sizes the views actually drew got their own rungs, which is the change that made
the scale describe the app rather than the reverse.
- The 499/18 figures are not comparable to anything measurable, for the population reason above.
What is left
172 bare TextBlocks are drawn at a size that already has a token:
| Raw size |
Token it should use |
Count |
Files |
| 11 |
Caption |
83 |
28 |
| 12 |
Subtle |
67 |
26 |
| 14 |
SectionTitle |
22 |
11 |
41 distinct view files are involved.
A further 48 sit at a size with no token at all: 10 (×24), 15 (×16), 9 (×7), 24 (×1 — About's product name,
deliberately left; no text rung sits within two points of it).
The prerequisite that blocks the sweep
Caption, Subtle, SectionTitle, SectionLabel and Display do not carry
BasedOn="{StaticResource {x:Type TextBlock}}". They restate Foreground and FontFamily instead. In WPF a
keyed style replaces the keyless <Style TargetType="TextBlock"> rather than merging with it, so applying
one of these five to an element that currently has no style silently drops the two setters they do not
restate:
<Setter Property="TextOptions.TextFormattingMode" Value="Ideal"/>
<Setter Property="TextOptions.TextRenderingMode" Value="ClearType"/>
So "swap FontSize="11" for Style="{StaticResource Caption}"" is not the no-op it looks like. Doing all
172 today would change the text-rendering mode on 172 elements.
The fix is one attribute on each of the five styles. It is also the one edit in this issue that is not free:
adding BasedOn to Display moves 59 tab titles from TextFormattingMode=Display (the WPF default they get
today) to Ideal. At 28px that is the mode you want — Ideal uses fractional glyph advances and is what the
rest of the app already renders with — but it is a real change on every tab's title, so it wants a look on a
machine that runs the app rather than a silent merge.
Proposed order
- Add
BasedOn="{StaticResource {x:Type TextBlock}}" to Caption, Subtle, SectionTitle, SectionLabel
and Display, and drop the setters that BasedOn then supplies. Verify with
SysManager.IntegrationTests.TypographyTokenTests, which reads the resolved FontSize / FontWeight /
Foreground off a real TextBlock on the STA thread — XAML compiles either way, so nothing else can tell.
Screenshot check on the tab titles.
- Convert the 148 whose rendering the swap cannot change, so the edit is a size-attribute removal and
nothing else: 78 at 11, 55 at 12, 15 at 14. Either they name their own Foreground (which beats the
token's) or the token's colour is the TextPrimary they already inherit.
- Handle the remaining 24 individually, because each is a visible change and not a cleanup:
- 17 name no
Foreground and the token's differs from the inherited TextPrimary: 12 at 12 would become
TextSecondary, 5 at 11 would become TextMuted.
- 7 at 14 name no
FontWeight, so SectionTitle's SemiBold would bolden them.
- Decide whether 10, 15 and 9 deserve tokens or should move onto 11 / 14. 24 stays raw unless the product
name is up for discussion.
Step 1 is the merge-blocker for step 2. Steps 3 and 4 are per-site judgement, not a sweep.
Affected area
Cross-app — App.xaml typography block; 34 views.
Problem
Rewritten 2026-09-16 against current source. Every number below is re-derived from
Views/*.xaml+MainWindow.xamlwith XAML comments stripped, counting<TextBlock>start tags only and excludingelements that already name a
Styleor use an icon font (Segoe Fluent/Segoe MDL2, where aFontSizeis a glyph box rather than typography). 934 TextBlocks inspected. The original body counted every
FontSize="…"in the files, including icon glyphs,TextBox/ComboBoxand style definitions, which is whyits totals cannot be compared with these.
The number half of this issue is finished. The body-text half is not, and it turns out to be blocked
on a prerequisite that has to be settled first.
What shipped
The scale now has twelve named styles, and all of them are used:
SubtleDisplayBodySectionLabelSectionTitleCaptionMetricCompactMetricMetricLargeHeadingMetricSmallMetricHeroMetric rungs at 16 / 20 / 22 / 26 / 30, text at 28 / 20 / 14 / 13 / 12 / 11. Zero bare TextBlocks remain at
13, 18, 26, 28, 30 or 32.
ArchitectureTests.EveryMetricRungSize_IsReachedThroughItsRungholds that lineand also asserts every rung a view names is actually defined, which a compile cannot tell you because
{StaticResource}inside aDataTemplateresolves at runtime.Corrections to the original body
Headingwas never dead. It has 5 uses today, and the claim was wrong when filed: the grep behind itsearched for
StaticResource Headingwhere the markup writes{StaticResource Heading}.Metricdid not "match no shipped value". It had 10 uses when filed and has 12 now. It was neverre-pointed; instead the sizes the views actually drew got their own rungs, which is the change that made
the scale describe the app rather than the reverse.
What is left
172 bare TextBlocks are drawn at a size that already has a token:
CaptionSubtleSectionTitle41 distinct view files are involved.
A further 48 sit at a size with no token at all: 10 (×24), 15 (×16), 9 (×7), 24 (×1 — About's product name,
deliberately left; no text rung sits within two points of it).
The prerequisite that blocks the sweep
Caption,Subtle,SectionTitle,SectionLabelandDisplaydo not carryBasedOn="{StaticResource {x:Type TextBlock}}". They restateForegroundandFontFamilyinstead. In WPF akeyed style replaces the keyless
<Style TargetType="TextBlock">rather than merging with it, so applyingone of these five to an element that currently has no style silently drops the two setters they do not
restate:
So "swap
FontSize="11"forStyle="{StaticResource Caption}"" is not the no-op it looks like. Doing all172 today would change the text-rendering mode on 172 elements.
The fix is one attribute on each of the five styles. It is also the one edit in this issue that is not free:
adding
BasedOntoDisplaymoves 59 tab titles fromTextFormattingMode=Display(the WPF default they gettoday) to
Ideal. At 28px that is the mode you want —Idealuses fractional glyph advances and is what therest of the app already renders with — but it is a real change on every tab's title, so it wants a look on a
machine that runs the app rather than a silent merge.
Proposed order
BasedOn="{StaticResource {x:Type TextBlock}}"toCaption,Subtle,SectionTitle,SectionLabeland
Display, and drop the setters thatBasedOnthen supplies. Verify withSysManager.IntegrationTests.TypographyTokenTests, which reads the resolvedFontSize/FontWeight/Foregroundoff a real TextBlock on the STA thread — XAML compiles either way, so nothing else can tell.Screenshot check on the tab titles.
nothing else: 78 at 11, 55 at 12, 15 at 14. Either they name their own
Foreground(which beats thetoken's) or the token's colour is the
TextPrimarythey already inherit.Foregroundand the token's differs from the inheritedTextPrimary: 12 at 12 would becomeTextSecondary, 5 at 11 would becomeTextMuted.FontWeight, soSectionTitle'sSemiBoldwould bolden them.name is up for discussion.
Step 1 is the merge-blocker for step 2. Steps 3 and 4 are per-site judgement, not a sweep.
Affected area
Cross-app —
App.xamltypography block; 34 views.