diff --git a/App.xaml b/App.xaml
index 0c688a0..396eaf0 100644
--- a/App.xaml
+++ b/App.xaml
@@ -1,13 +1,16 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DESIGN.md b/DESIGN.md
index f5028ab..1f3bbc3 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -171,7 +171,11 @@ rejected in favour of the values above.
## Implementation status
-- **Not yet wired.** `Themes/Controls.xaml` still carries the legacy palette and the dozens of
- hardcoded hexes; `Themes/Tokens.xaml` lands next, then surfaces are converted one at a time.
-- Target end state: every colour, radius and spacing value resolves from `Tokens.xaml`; a guard
- test fails on any raw `#hex` outside it.
+- **`Themes/Tokens.xaml` exists and is merged first in `App.xaml`** (2026-10-30). Guard:
+ `ytLive.Tests/DesignTokensTests.cs` — it parses this document and fails if a documented token has
+ no brush, if a value disagrees, or if the retired `#e94560` returns as a token.
+- **Still to sweep:** `Themes/Controls.xaml` and the dozens of hardcoded hexes in XAML/C# still use
+ the legacy palette. Surfaces convert one at a time; the end state is that every colour, radius and
+ spacing value resolves from `Tokens.xaml` and a guard fails on any raw `#hex` outside it.
+- Fonts (Inter, JetBrains Mono) are declared as token families with system fallbacks; the bundled
+ font assets land next.
diff --git a/Themes/Tokens.xaml b/Themes/Tokens.xaml
new file mode 100644
index 0000000..11d28ad
--- /dev/null
+++ b/Themes/Tokens.xaml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Inter, Segoe UI
+ JetBrains Mono, Cascadia Mono, Consolas
+
+ 32
+ 24
+ 18
+ 15
+ 13
+ 12
+ 13
+ 11
+ 10
+
+
+ 4
+ 8
+ 12
+ 16
+ 24
+ 12
+ 16
+ 8
+
+
+ 4
+ 8
+ 16
+
+
+ 0:0:0.09
+ 0:0:0.15
+
+
diff --git a/ytLive.Tests/DesignTokensTests.cs b/ytLive.Tests/DesignTokensTests.cs
new file mode 100644
index 0000000..268ca7a
--- /dev/null
+++ b/ytLive.Tests/DesignTokensTests.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using Xunit;
+
+namespace ytLive.Tests;
+
+/// DESIGN.md became the visual standard on 2026-10-30. This is its guard: the document
+/// is the contract, `Themes/Tokens.xaml` is the implementation, and a token documented in one but
+/// missing (or different) in the other fails here — so the palette cannot drift into two truths
+/// the way the 2026-10-26 and 2026-10-29 Stitch exports did.
+///
+/// Deliberately parses the markdown rather than duplicating a list: adding a colour to the
+/// standard requires implementing it, and changing a value in the theme without the document is a
+/// lie the gate catches.
+public class DesignTokensTests
+{
+ private static readonly XNamespace Xaml = "http://schemas.microsoft.com/winfx/2006/xaml";
+
+ [Fact]
+ public void Every_colour_in_the_standard_exists_as_a_token_brush_with_the_documented_value()
+ {
+ var documented = DocumentedColours();
+ Assert.True(documented.Count >= 20,
+ $"parsed only {documented.Count} colour tokens out of DESIGN.md — the table format changed");
+
+ var brushes = Brushes();
+
+ foreach (var (token, hex) in documented)
+ {
+ var key = "Brush." + Pascal(token);
+ Assert.True(brushes.ContainsKey(key),
+ $"DESIGN.md documents `{token}` ({hex}) but Themes/Tokens.xaml has no `{key}` brush");
+ Assert.Equal(hex, brushes[key], ignoreCase: true);
+ }
+ }
+
+ [Fact]
+ public void The_meter_bands_are_the_standard_three()
+ {
+ var brushes = Brushes();
+
+ // DESIGN.md -> "Meter thresholds": nominal to -12 dB, caution to -3 dB, red above.
+ Assert.Equal("#38bdf8", brushes["Brush.Meter.Nominal"], ignoreCase: true);
+ Assert.Equal("#ffb95f", brushes["Brush.Meter.Caution"], ignoreCase: true);
+ Assert.Equal("#ff5451", brushes["Brush.Meter.Peak"], ignoreCase: true);
+ }
+
+ [Fact]
+ public void Tokens_merge_before_the_theme_so_styles_can_resolve_them()
+ {
+ var app = XDocument.Load(SourceAssert.RepoPath("App.xaml"));
+ var sources = app.Descendants()
+ .Where(e => e.Name.LocalName == "ResourceDictionary")
+ .Select(e => (string?)e.Attribute("Source"))
+ .Where(s => s != null)
+ .ToList();
+
+ var tokens = sources.FindIndex(s => s!.EndsWith("Themes/Tokens.xaml", StringComparison.Ordinal));
+ var controls = sources.FindIndex(s => s!.EndsWith("Themes/Controls.xaml", StringComparison.Ordinal));
+
+ Assert.True(tokens >= 0, "App.xaml does not merge Themes/Tokens.xaml");
+ Assert.True(controls >= 0, "App.xaml no longer merges Themes/Controls.xaml");
+ Assert.True(tokens < controls,
+ "Themes/Tokens.xaml must merge BEFORE Themes/Controls.xaml, or styles cannot resolve a token");
+ }
+
+ [Fact]
+ public void The_retired_brand_red_is_not_a_token()
+ {
+ // #e94560 was the general accent in 15 files. It is retired (DESIGN.md, 2026-10-30):
+ // blue is interactive, red means live/record. Re-introducing it as a token would undo
+ // the rule, so it must not appear anywhere in the token sheet.
+ Assert.DoesNotContain("#e94560", File.ReadAllText(SourceAssert.RepoPath("Themes", "Tokens.xaml")),
+ StringComparison.OrdinalIgnoreCase);
+ }
+
+ /// The `| Token | Hex |` tables under "## Colour", stopping at "## Typography".
+ /// The "Rejected from the export" table lives further down and must not count.
+ private static Dictionary DocumentedColours()
+ {
+ var result = new Dictionary(StringComparer.Ordinal);
+ var inColour = false;
+
+ foreach (var raw in File.ReadAllLines(SourceAssert.RepoPath("DESIGN.md")))
+ {
+ var line = raw.Trim();
+ if (line.StartsWith("## ", StringComparison.Ordinal))
+ {
+ inColour = line.StartsWith("## Colour", StringComparison.Ordinal);
+ continue;
+ }
+ if (!inColour || !line.StartsWith("| `", StringComparison.Ordinal)) continue;
+
+ var cells = line.Split('|').Select(c => c.Trim()).Where(c => c.Length > 0).ToArray();
+ if (cells.Length < 2) continue;
+
+ // The token cell may carry a note after the backticked name("`tertiary` (electric blue)").
+ var token = Backticked(cells[0]);
+ var hex = Backticked(cells[1]);
+ if (!hex.StartsWith("#", StringComparison.Ordinal) || hex.Length != 7) continue;
+ result[token] = hex;
+ }
+
+ return result;
+ }
+
+ private static Dictionary Brushes()
+ {
+ var dict = XDocument.Load(SourceAssert.RepoPath("Themes", "Tokens.xaml"));
+ return dict.Descendants()
+ .Where(e => e.Name.LocalName == "SolidColorBrush")
+ .Select(e => ((string?)e.Attribute(Xaml + "Key"), (string?)e.Attribute("Color")))
+ .Where(p => p.Item1 != null && p.Item2 != null)
+ .ToDictionary(p => p.Item1!, p => p.Item2!, StringComparer.Ordinal);
+ }
+
+ /// The first `backticked` run in a markdown cell, or the trimmed cell itself.
+ private static string Backticked(string cell)
+ {
+ var start = cell.IndexOf('`');
+ if (start < 0) return cell.Trim();
+ var end = cell.IndexOf('`', start + 1);
+ return end < 0 ? cell.Trim() : cell[(start + 1)..end].Trim();
+ }
+
+ /// `surface-container-lowest` → `SurfaceContainerLowest`.
+ private static string Pascal(string token) =>
+ string.Concat(token.Split('-', StringSplitOptions.RemoveEmptyEntries)
+ .Select(part => char.ToUpperInvariant(part[0]) + part[1..]));
+}