Skip to content

chore: put the four body-text tokens on the implicit style, then adopt them - #2344

Merged
laurentiu021 merged 1 commit into
mainfrom
chore/typography-tokens-basedon
Sep 17, 2026
Merged

laurentiu021 merged 1 commit into
mainfrom
chore/typography-tokens-basedon

Conversation

@laurentiu021

Copy link
Copy Markdown
Owner

The prerequisite

#1634's remaining half was blocked on something, not just unfinished. Caption, Subtle, SectionTitle and SectionLabel carried no BasedOn, and in WPF an explicit keyed style replaces the keyless <Style TargetType="TextBlock"> rather than merging with it. So swapping a raw FontSize="11" for Style="{StaticResource Caption}" looked like a no-op and was not — the element gave up TextRenderingMode=ClearType on the way. 172 elements were waiting behind that, and the type-scale guard says so in its own remarks: it deliberately did not cover 11, 12 and 14 for exactly this reason.

Step 1 — BasedOn on the four

Plus dropping the setters it then supplies: FontFamily on all four, and Foreground on SectionTitle, which restated the TextPrimary it now inherits. Caption, Subtle and SectionLabel keep their own colour because they deliberately differ from it.

SectionLabel had to move. BasedOn is a StaticResource reference, and a StaticResource cannot resolve a key defined later in the same dictionary — the keyless TextBlock style sits below where SectionLabel was declared, in the middle of the control templates. Left in place it would have thrown while App.xaml parsed, which is to say at startup. This machine never launches the app, so the resolved-value test is the only thing that would have caught it.

Step 2 — 145 conversions across 38 views

76 at 11, 54 at 12, 15 at 14. The 15 at 14 also drop FontWeight="SemiBold", because all fifteen already said exactly what SectionTitle sets and leaving it beside a style that declares the same thing is the drift this issue is about. Every edit is a size-attribute removal and nothing else: 145 insertions, 145 deletions, one line per element.

145 rather than the 148 the issue estimated, and neither number knew why. Five elements set Style through a <TextBlock.Style> child — a Style built inline for its Triggers — which an attribute scan cannot see. Giving those a Style attribute as well is MC3024: property has already been set and can be set only once. The compiler caught all five; the guard now skips them by inspecting the element body, so the next sweep will not find them either.

The 24 that would visibly change are untouched, as agreed. 17 name no Foreground at 11 or 12, where the token's colour differs from the inherited TextPrimary; 7 name no FontWeight at 14, where SectionTitle would bolden them. Those are steps 3 and 4 of the issue and they are per-site judgement, not a sweep.

A premise in the issue turned out to be wrong

#1634 proposed including Display because the 59 tab titles would move from TextFormattingMode=Display to Ideal. Measured on a real TextBlock: Ideal is WPF's default, so they already render that way. The only difference BasedOn would make to Display is TextRenderingMode, Auto to ClearType — the same one-property change the other four just took.

Left out anyway, because the decision to exclude it was taken on the larger claim and the smaller one deserves its own look at 28px. TypographyTokenTests.Display_StillStandsAloneAndStillResolvesItsColour asserts both values, so it fails the moment someone completes the set — which is the prompt to do that look first rather than an oversight sitting in the file.

The guard

EveryMetricRungSize_IsReachedThroughItsRung now covers 11, 12 and 14, conditionally — the same condition that decided which 145 were safe:

  • at 11 or 12, the token is required only of an element that names its own Foreground;
  • at 14, only of one that names its own FontWeight.

So the 24 pass without an allowlist to rot, and a new element that could have used a token fails. Its rung lookup also stopped requiring FontSize to be the first setter: Subtle declares Foreground first, so the old pattern would have silently dropped it from the dictionary and enforced a scale with a hole in it.

Verification

Red ritual — four mutations, each red for the right reason, green after restore, every file restored from saved bytes:

Mutation Result
a converted element put back to a raw 12 RED, names the file and the token
one of the deliberately-raw 24 given its own colour RED — the condition is a discriminator in both directions, not a one-way pass
a text token renamed out from under the guard RED on the floor, not silence
BasedOn removed from Caption RED in the resolved-value test, which a compile cannot do

The baseline is itself two proofs. 24 elements keep a raw 11/12/14 and are not flagged, so the condition is not a blanket rule. And the five <TextBlock.Style> elements name their own Foreground at 11 or 12 — so a scan that could not see the child element would flag all five, and the guard is green.

  • Unit suite: 5793 passed, 0 failed.
  • Integration typography tests: 15 passed (4 new).
  • All four projects: dotnet build -c Release, 0 errors, 0 warnings.
  • dotnet format --verify-no-changes: clean on tests and integration tests.
  • Leak scan: 43 patterns over 42 changed files, 0 hits.
  • CRLF preserved across the change set.

No release

No version bump and no CHANGELOG entry: nothing a user can see changes, which is the claim the resolved-value test exists to support. AllTabsSmokeUiTests navigates all 59 tabs in CI, so a {StaticResource} that failed to resolve at runtime — the failure mode a compile cannot catch — would fail there.

Refs #1634 — steps 3 and 4 remain.

…t them

#1634's remaining half was blocked on a prerequisite rather than on effort.
Caption, Subtle, SectionTitle and SectionLabel carried no BasedOn, and in WPF an
explicit keyed style REPLACES the keyless <Style TargetType="TextBlock"> instead
of merging with it. So swapping a raw FontSize="11" for Style="{StaticResource
Caption}" looked like a no-op and was not: the element gave up
TextRenderingMode=ClearType on the way. 172 elements were waiting behind that.

Step 1 — BasedOn on the four, dropping the setters it then supplies (FontFamily
on all four, Foreground on SectionTitle, which restated the inherited
TextPrimary). SectionLabel had to MOVE to the typography block: BasedOn is a
StaticResource reference, a StaticResource cannot resolve a key defined later in
the same dictionary, and the keyless TextBlock style sits below where SectionLabel
was. Left in place it would have thrown while App.xaml parsed, which is at
startup — and this machine never launches the app, so only the resolved-value
test would have caught it.

Step 2 — 145 conversions across 38 views: 76 at 11, 54 at 12, 15 at 14. The 15 at
14 also drop FontWeight="SemiBold", because all 15 already said exactly what
SectionTitle sets. Every one is a size-attribute removal and nothing else. The
count is 145 rather than the 148 the issue estimated, and neither number knew
about the 5 elements that set Style through a <TextBlock.Style> child for its
Triggers: an attribute scan cannot see those, and giving them a Style attribute as
well is "property has already been set and can be set only once". The compiler
caught all five; the guard now skips them by inspecting the element body.

The 24 where the token would visibly differ are untouched, as agreed — 17 name no
Foreground at 11 or 12, where the token's colour differs from the inherited
TextPrimary, and 7 name no FontWeight at 14, where SectionTitle would bolden
them. They need a look, not a sweep.

Display stays out, and a test pins that. #1634 proposed including it because the
59 tab titles would move from TextFormattingMode=Display to Ideal. Measured on a
real TextBlock, that premise is wrong: Ideal is WPF's default, so they already
render that way, and the only difference BasedOn would make is TextRenderingMode,
Auto to ClearType — the same one-property change the other four just took. Left
out anyway, because the decision was taken on the larger claim.

EveryMetricRungSize_IsReachedThroughItsRung now covers 11, 12 and 14 as well,
conditionally: at 11 or 12 the token is required only of an element that names its
own Foreground, at 14 only of one that names its own FontWeight. That is the same
condition that decided which 145 were safe, so the 24 pass without an allowlist to
rot, and a NEW element that could have used a token fails. Its rung lookup also
stopped requiring FontSize to be the first setter — Subtle writes Foreground
first, so the old pattern would have dropped it from the dictionary and enforced a
scale with a hole in it.

Red ritual, four cases, each red for the right reason and green after restore: a
converted element put back to a raw 12; one of the deliberately-raw 24 given its
own colour, which is what makes the swap required; a token renamed out from under
the guard (caught by the floor, not by silence); BasedOn removed from Caption,
which the resolved-value test catches and a compile cannot.

The baseline is itself two proofs. 24 elements keep a raw 11, 12 or 14 and are not
flagged, so the condition is not a blanket rule. And 5 elements set Style through a
child element AND name their own Foreground at 11 or 12, so a scan that could not
see the child would flag all five.

Unit suite 5774 passed, 0 failed. Integration typography tests 15 passed. All four
projects build with 0 warnings. No version bump and no CHANGELOG entry: nothing a
user can see changes, which is the claim the resolved-value test exists to support.

Refs #1634 — steps 3 and 4 remain, and both are per-site judgement.
@laurentiu021
laurentiu021 merged commit 062fc19 into main Sep 17, 2026
6 checks passed
@laurentiu021
laurentiu021 deleted the chore/typography-tokens-basedon branch September 17, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant