MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 99.4% 180 / 0 / 181
Functions: 96.9% 31 / 0 / 32
Branches: 87.0% 368 / 0 / 423

presentation/DesignTokens.cpp
Line Branch Exec Source
1 #include "DesignTokens.h"
2 #include <QFontDatabase>
3 #include <QFile>
4 #include <QFont>
5 #include <QFontMetrics>
6 #include <QGuiApplication>
7 #include <QJsonDocument>
8 #include <QStyleHints>
9 #include <QColor>
10 #include <QRegularExpression>
11 #include <array>
12 #include <cmath>
13
14 namespace {
15 494500 double luminance(const QColor &color) {
16
2/2
✓ Branch 2 → 3 taken 10013 times.
✓ Branch 2 → 4 taken 1473487 times.
1483500 const auto linear = [](double c) { return c <= .04045 ? c / 12.92 : std::pow((c + .055) / 1.055, 2.4); };
17 494500 return .2126 * linear(color.redF()) + .7152 * linear(color.greenF()) + .0722 * linear(color.blueF());
18 }
19 247250 double contrast(const QColor &a, const QColor &b) {
20 247250 const double first = luminance(a), second = luminance(b);
21 247250 return (qMax(first, second) + .05) / (qMin(first, second) + .05);
22 }
23 231636 QColor blend(const QColor &base, const QColor &tint, double amount) {
24 463272 return QColor(qRound(base.red() * (1 - amount) + tint.red() * amount),
25 231636 qRound(base.green() * (1 - amount) + tint.green() * amount),
26 694908 qRound(base.blue() * (1 - amount) + tint.blue() * amount));
27 }
28 // OKLab distance × 100 (Björn Ottosson's matrices), the measure of
29 // docs/design/sprint-101/chart_palette.py.
30 3083 double oklabDistance(const QColor &a, const QColor &b) {
31 6166 const auto lab = [](const QColor &color) {
32
2/2
✓ Branch 2 → 3 taken 1508 times.
✓ Branch 2 → 4 taken 16990 times.
18498 const auto linear = [](double c) { return c <= .04045 ? c / 12.92 : std::pow((c + .055) / 1.055, 2.4); };
33 6166 const double r = linear(color.redF()), g = linear(color.greenF()), bl = linear(color.blueF());
34 6166 const double l = std::cbrt(.4122214708 * r + .5363325363 * g + .0514459929 * bl);
35 6166 const double m = std::cbrt(.2119034982 * r + .6806995451 * g + .1073969566 * bl);
36 6166 const double s = std::cbrt(.0883024619 * r + .2817188376 * g + .6299787005 * bl);
37 6166 return std::array<double, 3>{.2104542553 * l + .7936177850 * m - .0040720468 * s,
38 6166 1.9779984951 * l - 2.4285922050 * m + .4505937099 * s,
39 6166 .0259040371 * l + .7827717662 * m - .8086757660 * s};
40 };
41 3083 const auto x = lab(a), y = lab(b);
42 3083 return 100 * std::sqrt(std::pow(x[0] - y[0], 2) + std::pow(x[1] - y[1], 2) + std::pow(x[2] - y[2], 2));
43 }
44 // SD-14: the timeline's bar colour for an accent on the timeline card —
45 // the lightest tint with 3:1 against the card, desaturated towards the
46 // neutral `secondary` only as far as needed to stay ΔE ≥ 16 from the accent
47 // (hover, focus and the selected bucket use the accent itself).
48 1432 QColor chartBarFor(const QColor &card, const QColor &accent, const QColor &neutral) {
49 1432 QColor bar;
50 1432 double tint = .05;
51
2/2
✓ Branch 18 → 3 taken 3083 times.
✓ Branch 18 → 19 taken 83 times.
3166 for (int step = 0; step <= 12; ++step) {
52 3083 const QColor base = blend(accent, neutral, step * .05);
53 3083 tint = .05;
54
6/7
✓ Branch 6 → 7 taken 184481 times.
✗ Branch 6 → 11 not taken.
✓ Branch 8 → 9 taken 184481 times.
✓ Branch 9 → 10 taken 181398 times.
✓ Branch 9 → 11 taken 3083 times.
✓ Branch 12 → 5 taken 181398 times.
✓ Branch 12 → 13 taken 3083 times.
184481 while (tint < 1 && contrast(blend(card, base, tint), card) < 3.0) tint += .01;
55 3083 bar = blend(card, base, tint);
56
3/3
✓ Branch 14 → 15 taken 3083 times.
✓ Branch 15 → 16 taken 1349 times.
✓ Branch 15 → 17 taken 1734 times.
3083 if (oklabDistance(bar, accent) >= 16) break;
57 }
58 1432 return bar;
59 }
60 }
61
62
1/1
✓ Branch 15 → 16 taken 894 times.
2682 DesignTokens::DesignTokens(QObject *parent) : QObject(parent) {
63
1/1
✓ Branch 16 → 17 taken 894 times.
894 Q_INIT_RESOURCE(precision_tokens);
64
1/1
✓ Branch 20 → 21 taken 894 times.
894 QFile file(QStringLiteral(":/themes/precision.json"));
65
2/3
✓ Branch 24 → 25 taken 894 times.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 28 taken 894 times.
894 if (!file.open(QIODevice::ReadOnly)) qFatal("Missing Precision design tokens");
66
4/4
✓ Branch 28 → 29 taken 894 times.
✓ Branch 29 → 30 taken 894 times.
✓ Branch 30 → 31 taken 894 times.
✓ Branch 31 → 32 taken 894 times.
894 m_palettes = QJsonDocument::fromJson(file.readAll()).toVariant().toMap();
67
9/18
✓ Branch 37 → 38 taken 894 times.
✓ Branch 38 → 39 taken 894 times.
✓ Branch 39 → 40 taken 894 times.
✗ Branch 39 → 43 not taken.
✓ Branch 40 → 41 taken 894 times.
✓ Branch 41 → 42 taken 894 times.
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 44 taken 894 times.
✓ Branch 45 → 46 taken 894 times.
✗ Branch 45 → 47 not taken.
✓ Branch 47 → 48 taken 894 times.
✗ Branch 47 → 49 not taken.
✗ Branch 49 → 50 not taken.
✓ Branch 49 → 52 taken 894 times.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
894 if (!m_palettes.contains("light") || !m_palettes.contains("dark")) qFatal("Invalid Precision design tokens");
68
1/1
✓ Branch 53 → 54 taken 894 times.
894 connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this,
69
1/3
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✓ Branch 52 → 53 taken 894 times.
894 [this] { if (m_theme == "system") emit changed(); });
70
2/2
✓ Branch 55 → 56 taken 894 times.
✓ Branch 56 → 57 taken 894 times.
894 connect(QGuiApplication::styleHints(), &QStyleHints::wheelScrollLinesChanged, this, &DesignTokens::changed);
71 // B3: a live desktop preference change re-evaluates a "system" policy.
72 894 connect(&m_motion, &SystemMotionPreference::changed,
73
1/1
✓ Branch 58 → 59 taken 894 times.
894 this, &DesignTokens::refreshMotionFromSystem);
74 894 }
75 2840499 QVariantMap DesignTokens::palette() const {
76
4/4
✓ Branch 3 → 4 taken 2545300 times.
✓ Branch 3 → 9 taken 295199 times.
✓ Branch 5 → 6 taken 1942113 times.
✓ Branch 5 → 10 taken 603187 times.
4782612 const bool dark = m_theme == "dark" || (m_theme == "system" &&
77
3/4
✓ Branch 6 → 7 taken 1942113 times.
✓ Branch 7 → 8 taken 1942113 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1942113 times.
1942113 QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark);
78
7/11
✓ Branch 11 → 12 taken 295199 times.
✓ Branch 11 → 15 taken 2545300 times.
✓ Branch 18 → 19 taken 2840499 times.
✓ Branch 20 → 21 taken 2545300 times.
✓ Branch 20 → 22 taken 295199 times.
✓ Branch 22 → 23 taken 295199 times.
✓ Branch 22 → 24 taken 2545300 times.
✗ Branch 313 → 314 not taken.
✗ Branch 313 → 315 not taken.
✗ Branch 317 → 318 not taken.
✗ Branch 317 → 319 not taken.
5680998 const QString key = (dark ? QStringLiteral("dark") : QStringLiteral("light")) + m_accentColor;
79
2/2
✓ Branch 25 → 26 taken 2839067 times.
✓ Branch 25 → 27 taken 1432 times.
2840499 if (key == m_paletteKey) return m_cachedPalette;
80
5/5
✓ Branch 28 → 29 taken 313 times.
✓ Branch 28 → 30 taken 1119 times.
✓ Branch 31 → 32 taken 1432 times.
✓ Branch 32 → 33 taken 1432 times.
✓ Branch 33 → 34 taken 1432 times.
1432 auto result = m_palettes.value(dark ? "dark" : "light").toMap();
81
2/2
✓ Branch 38 → 39 taken 449 times.
✓ Branch 38 → 133 taken 983 times.
1432 if (!m_accentColor.isEmpty()) {
82 449 const QColor requested(m_accentColor);
83
3/3
✓ Branch 41 → 42 taken 449 times.
✓ Branch 42 → 43 taken 449 times.
✓ Branch 43 → 44 taken 449 times.
449 const auto extremes = m_palettes.value("contrast").toMap();
84
6/6
✓ Branch 48 → 49 taken 449 times.
✓ Branch 49 → 50 taken 449 times.
✓ Branch 50 → 51 taken 449 times.
✓ Branch 57 → 58 taken 449 times.
✓ Branch 58 → 59 taken 449 times.
✓ Branch 59 → 60 taken 449 times.
449 const QColor light(extremes.value("light").toString()), darkEndpoint(extremes.value("dark").toString());
85
6/6
✓ Branch 65 → 66 taken 449 times.
✓ Branch 66 → 67 taken 449 times.
✓ Branch 67 → 68 taken 449 times.
✓ Branch 70 → 71 taken 449 times.
✓ Branch 72 → 73 taken 449 times.
✓ Branch 73 → 74 taken 449 times.
449 result["selection"] = blend(QColor(result["surface"].toString()), requested, .12).name();
86 // Keep the chosen hue while adapting its luminance to every surface
87 // on which accent text or focus indicators are drawn. Cache the result;
88 // QML palette reads must not repeat this bounded contrast calculation.
89 449 QColor accent = requested;
90
1/2
✓ Branch 105 → 81 taken 29296 times.
✗ Branch 105 → 106 not taken.
29296 for (int step = 0; step <= 255; ++step) {
91
2/2
✓ Branch 81 → 82 taken 14211 times.
✓ Branch 81 → 83 taken 15085 times.
29296 accent = blend(requested, dark ? light : darkEndpoint, step / 255.0);
92 29296 bool readable = true;
93
2/2
✓ Branch 101 → 87 taken 117184 times.
✓ Branch 101 → 102 taken 29296 times.
146480 for (const auto *background : {"surface", "sidebar", "selection", "hover"})
94
12/16
✓ Branch 87 → 88 taken 54663 times.
✓ Branch 87 → 95 taken 62521 times.
✓ Branch 88 → 89 taken 54663 times.
✓ Branch 89 → 90 taken 54663 times.
✓ Branch 90 → 91 taken 54663 times.
✓ Branch 92 → 93 taken 54663 times.
✓ Branch 93 → 94 taken 25816 times.
✓ Branch 93 → 95 taken 28847 times.
✓ Branch 96 → 97 taken 54663 times.
✓ Branch 96 → 98 taken 62521 times.
✓ Branch 98 → 99 taken 54663 times.
✓ Branch 98 → 100 taken 62521 times.
✗ Branch 378 → 379 not taken.
✗ Branch 378 → 380 not taken.
✗ Branch 382 → 383 not taken.
✗ Branch 382 → 384 not taken.
117184 readable = readable && contrast(accent, QColor(result[background].toString())) >= 4.5;
95
2/2
✓ Branch 102 → 103 taken 449 times.
✓ Branch 102 → 104 taken 28847 times.
29296 if (readable) break;
96 }
97
6/6
✓ Branch 106 → 107 taken 449 times.
✓ Branch 108 → 109 taken 449 times.
✓ Branch 109 → 110 taken 449 times.
✓ Branch 111 → 112 taken 449 times.
✓ Branch 112 → 113 taken 449 times.
✓ Branch 113 → 114 taken 449 times.
449 result["accent"] = result["focus"] = accent.name();
98
4/4
✓ Branch 118 → 119 taken 449 times.
✓ Branch 119 → 120 taken 449 times.
✓ Branch 125 → 126 taken 449 times.
✓ Branch 126 → 127 taken 449 times.
1347 result["onAccent"] = (contrast(accent, light) >= contrast(accent, darkEndpoint)
99
3/3
✓ Branch 120 → 121 taken 222 times.
✓ Branch 120 → 122 taken 227 times.
✓ Branch 123 → 124 taken 449 times.
1347 ? light : darkEndpoint).name();
100 449 }
101
8/8
✓ Branch 133 → 134 taken 1432 times.
✓ Branch 134 → 135 taken 1432 times.
✓ Branch 135 → 136 taken 1432 times.
✓ Branch 136 → 137 taken 1432 times.
✓ Branch 137 → 138 taken 1432 times.
✓ Branch 138 → 139 taken 1432 times.
✓ Branch 139 → 140 taken 1432 times.
✓ Branch 140 → 141 taken 1432 times.
1432 result["taskDoneFill"] = result["dueToday"] = result["accent"];
102 // Sprint 101 SD-14 (timeline redesign, revises D-10): the bars are a
103 // quiet, solid tint of the accent on the timeline's card (surfaceSubtle);
104 // the full accent marks hover, keyboard focus and the selected bucket.
105 // docs/design/sprint-101/chart_palette.py checks every preset.
106
3/3
✓ Branch 144 → 145 taken 1432 times.
✓ Branch 145 → 146 taken 1432 times.
✓ Branch 146 → 147 taken 1432 times.
1432 const QColor card(result["surfaceSubtle"].toString());
107
10/10
✓ Branch 150 → 151 taken 1432 times.
✓ Branch 151 → 152 taken 1432 times.
✓ Branch 152 → 153 taken 1432 times.
✓ Branch 154 → 155 taken 1432 times.
✓ Branch 155 → 156 taken 1432 times.
✓ Branch 156 → 157 taken 1432 times.
✓ Branch 158 → 159 taken 1432 times.
✓ Branch 159 → 160 taken 1432 times.
✓ Branch 161 → 162 taken 1432 times.
✓ Branch 162 → 163 taken 1432 times.
1432 result["chartBar"] = chartBarFor(card, QColor(result["accent"].toString()), QColor(result["secondary"].toString())).name();
108
8/8
✓ Branch 171 → 172 taken 1432 times.
✓ Branch 172 → 173 taken 1432 times.
✓ Branch 173 → 174 taken 1432 times.
✓ Branch 174 → 175 taken 1432 times.
✓ Branch 175 → 176 taken 1432 times.
✓ Branch 176 → 177 taken 1432 times.
✓ Branch 177 → 178 taken 1432 times.
✓ Branch 178 → 179 taken 1432 times.
1432 result["chartBarHover"] = result["chartBarSelected"] = result["accent"];
109
5/5
✓ Branch 182 → 183 taken 1432 times.
✓ Branch 183 → 184 taken 1432 times.
✓ Branch 184 → 185 taken 1432 times.
✓ Branch 185 → 186 taken 1432 times.
✓ Branch 186 → 187 taken 1432 times.
1432 result["chartBrush"] = result["selection"];
110
5/5
✓ Branch 189 → 190 taken 1432 times.
✓ Branch 190 → 191 taken 1432 times.
✓ Branch 191 → 192 taken 1432 times.
✓ Branch 192 → 193 taken 1432 times.
✓ Branch 193 → 194 taken 1432 times.
1432 result["chartAxis"] = result["divider"];
111 // F94-60: every second task row, a quiet tint of the surface towards the
112 // text colour (3 %), so rows separate without divider lines.
113
9/9
✓ Branch 196 → 197 taken 1432 times.
✓ Branch 197 → 198 taken 1432 times.
✓ Branch 198 → 199 taken 1432 times.
✓ Branch 200 → 201 taken 1432 times.
✓ Branch 201 → 202 taken 1432 times.
✓ Branch 202 → 203 taken 1432 times.
✓ Branch 205 → 206 taken 1432 times.
✓ Branch 207 → 208 taken 1432 times.
✓ Branch 208 → 209 taken 1432 times.
1432 result["rowAlternate"] = blend(QColor(result["surface"].toString()), QColor(result["text"].toString()), .03).name();
114 // Sprint 108 §4.13: the composer's tokens, derived so every preset and
115 // custom accent keeps them readable (contrast values in DESIGN.md §16).
116
6/6
✓ Branch 217 → 218 taken 1432 times.
✓ Branch 218 → 219 taken 1432 times.
✓ Branch 219 → 220 taken 1432 times.
✓ Branch 223 → 224 taken 1432 times.
✓ Branch 224 → 225 taken 1432 times.
✓ Branch 225 → 226 taken 1432 times.
1432 const QColor surface(result["surface"].toString()), accentColor(result["accent"].toString());
117
8/8
✓ Branch 229 → 230 taken 1432 times.
✓ Branch 230 → 231 taken 1432 times.
✓ Branch 231 → 232 taken 1432 times.
✓ Branch 232 → 233 taken 1432 times.
✓ Branch 233 → 234 taken 1432 times.
✓ Branch 234 → 235 taken 1432 times.
✓ Branch 235 → 236 taken 1432 times.
✓ Branch 236 → 237 taken 1432 times.
1432 result["spellError"] = result["chipInvalid"] = result["danger"];
118
5/5
✓ Branch 240 → 241 taken 313 times.
✓ Branch 240 → 242 taken 1119 times.
✓ Branch 244 → 245 taken 1432 times.
✓ Branch 246 → 247 taken 1432 times.
✓ Branch 247 → 248 taken 1432 times.
1432 result["freeBusyBusy"] = blend(accentColor, surface, dark ? .35 : .25).name();
119
3/3
✓ Branch 253 → 254 taken 1432 times.
✓ Branch 255 → 256 taken 1432 times.
✓ Branch 256 → 257 taken 1432 times.
1432 result["freeBusyTentative"] = blend(accentColor, surface, .55).name();
120
5/5
✓ Branch 261 → 262 taken 1432 times.
✓ Branch 262 → 263 taken 1432 times.
✓ Branch 263 → 264 taken 1432 times.
✓ Branch 264 → 265 taken 1432 times.
✓ Branch 265 → 266 taken 1432 times.
1432 result["freeBusyUnknown"] = result["border"];
121
5/5
✓ Branch 268 → 269 taken 1432 times.
✓ Branch 269 → 270 taken 1432 times.
✓ Branch 270 → 271 taken 1432 times.
✓ Branch 271 → 272 taken 1432 times.
✓ Branch 272 → 273 taken 1432 times.
1432 result["meetingSlot"] = result["accent"];
122
5/5
✓ Branch 275 → 276 taken 1432 times.
✓ Branch 276 → 277 taken 1432 times.
✓ Branch 277 → 278 taken 1432 times.
✓ Branch 278 → 279 taken 1432 times.
✓ Branch 279 → 280 taken 1432 times.
1432 result["codeBackground"] = result["surfaceSubtle"];
123
4/4
✓ Branch 283 → 284 taken 1432 times.
✓ Branch 284 → 285 taken 1432 times.
✓ Branch 285 → 286 taken 1432 times.
✓ Branch 286 → 287 taken 1432 times.
1432 result["composeTextColors"] = m_palettes.value("composeTextColors");
124
4/4
✓ Branch 293 → 294 taken 1432 times.
✓ Branch 294 → 295 taken 1432 times.
✓ Branch 295 → 296 taken 1432 times.
✓ Branch 296 → 297 taken 1432 times.
1432 result["composeHighlightColors"] = m_palettes.value("composeHighlightColors");
125 1432 m_paletteKey = key;
126 1432 m_cachedPalette = result;
127 1432 return result;
128 2840499 }
129 260 double DesignTokens::contrastRatio(const QString &first, const QString &second) {
130
1/1
✓ Branch 4 → 5 taken 260 times.
260 return contrast(QColor(first), QColor(second));
131 }
132
133 1737 QVariantMap DesignTokens::eventColors(const QString &calendarColor) const {
134
1/1
✓ Branch 2 → 3 taken 1737 times.
1737 const auto palette = this->palette();
135
3/3
✓ Branch 3 → 4 taken 1737 times.
✓ Branch 4 → 5 taken 1737 times.
✓ Branch 5 → 6 taken 1737 times.
1737 const bool dark = QColor(palette["surface"].toString()).lightnessF() < .5;
136
3/3
✓ Branch 12 → 13 taken 1737 times.
✓ Branch 13 → 14 taken 1737 times.
✓ Branch 14 → 15 taken 1737 times.
1737 const auto tokens = m_palettes.value("calendar").toMap();
137
10/10
✓ Branch 19 → 20 taken 1737 times.
✓ Branch 20 → 21 taken 1737 times.
✓ Branch 21 → 22 taken 1737 times.
✓ Branch 22 → 23 taken 335 times.
✓ Branch 22 → 24 taken 1402 times.
✓ Branch 26 → 27 taken 335 times.
✓ Branch 26 → 28 taken 1402 times.
✓ Branch 29 → 30 taken 1737 times.
✓ Branch 30 → 31 taken 1737 times.
✓ Branch 31 → 32 taken 1737 times.
1737 const double alpha = tokens.value("eventTintAlpha").toMap().value(dark ? "dark" : "light", dark ? .28 : .18).toDouble();
138
3/3
✓ Branch 40 → 41 taken 1737 times.
✓ Branch 41 → 42 taken 1737 times.
✓ Branch 42 → 43 taken 1737 times.
1737 const double hoverStep = tokens.value("hoverTintStep", .06).toDouble();
139 1737 QColor bar(calendarColor);
140
5/5
✓ Branch 48 → 49 taken 22 times.
✓ Branch 48 → 57 taken 1715 times.
✓ Branch 49 → 50 taken 22 times.
✓ Branch 50 → 51 taken 22 times.
✓ Branch 51 → 52 taken 22 times.
1737 if (!bar.isValid()) bar = QColor(palette["accent"].toString());
141
3/3
✓ Branch 57 → 58 taken 1737 times.
✓ Branch 58 → 59 taken 1737 times.
✓ Branch 59 → 60 taken 1737 times.
1737 const QColor surface(palette["surface"].toString());
142 1737 const QColor background = blend(surface, bar, alpha);
143 1737 const QColor hover = blend(surface, bar, alpha + hoverStep);
144
3/3
✓ Branch 67 → 68 taken 1737 times.
✓ Branch 68 → 69 taken 1737 times.
✓ Branch 69 → 70 taken 1737 times.
1737 const auto extremes = m_palettes.value("contrast").toMap();
145
20/32
✓ Branch 73 → 74 taken 335 times.
✓ Branch 73 → 78 taken 1402 times.
✓ Branch 75 → 76 taken 335 times.
✓ Branch 76 → 77 taken 335 times.
✓ Branch 77 → 82 taken 335 times.
✓ Branch 79 → 80 taken 1402 times.
✓ Branch 80 → 81 taken 1402 times.
✓ Branch 81 → 82 taken 1402 times.
✓ Branch 84 → 85 taken 1402 times.
✓ Branch 84 → 86 taken 335 times.
✓ Branch 86 → 87 taken 1402 times.
✓ Branch 86 → 88 taken 335 times.
✓ Branch 88 → 89 taken 1402 times.
✓ Branch 88 → 90 taken 335 times.
✓ Branch 90 → 91 taken 335 times.
✓ Branch 90 → 92 taken 1402 times.
✓ Branch 92 → 93 taken 335 times.
✓ Branch 92 → 94 taken 1402 times.
✓ Branch 94 → 95 taken 335 times.
✓ Branch 94 → 96 taken 1402 times.
✗ Branch 207 → 208 not taken.
✗ Branch 207 → 209 not taken.
✗ Branch 211 → 212 not taken.
✗ Branch 211 → 213 not taken.
✗ Branch 215 → 216 not taken.
✗ Branch 215 → 217 not taken.
✗ Branch 219 → 220 not taken.
✗ Branch 219 → 221 not taken.
✗ Branch 223 → 224 not taken.
✗ Branch 223 → 225 not taken.
✗ Branch 227 → 228 not taken.
✗ Branch 227 → 229 not taken.
1737 const QColor endpoint(dark ? extremes.value("light").toString() : extremes.value("dark").toString());
146 // Move a candidate towards the theme's extreme until it reads on both
147 // backgrounds (bounded loop, at most 256 steps).
148 3474 const auto readable = [&](const QColor &candidate) {
149
1/2
✓ Branch 14 → 3 taken 3474 times.
✗ Branch 14 → 15 not taken.
3474 for (int step = 0; step <= 255; ++step) {
150 3474 const QColor color = blend(candidate, endpoint, step / 255.0);
151
5/8
✓ Branch 4 → 5 taken 3474 times.
✓ Branch 5 → 6 taken 3474 times.
✗ Branch 5 → 9 not taken.
✓ Branch 6 → 7 taken 3474 times.
✓ Branch 7 → 8 taken 3474 times.
✗ Branch 7 → 9 not taken.
✓ Branch 10 → 11 taken 3474 times.
✗ Branch 10 → 12 not taken.
3474 if (contrast(color, background) >= 4.5 && contrast(color, hover) >= 4.5) return color;
152 }
153 ✗ return endpoint;
154 1737 };
155 1737 return {{"background", background.name()}, {"hover", hover.name()}, {"bar", bar.name()},
156
4/4
✓ Branch 103 → 104 taken 1737 times.
✓ Branch 104 → 105 taken 1737 times.
✓ Branch 106 → 107 taken 1737 times.
✓ Branch 107 → 108 taken 1737 times.
3474 {"text", readable(QColor(palette["text"].toString())).name()},
157
6/8
✓ Branch 110 → 111 taken 1737 times.
✓ Branch 111 → 112 taken 1737 times.
✓ Branch 113 → 114 taken 1737 times.
✓ Branch 114 → 115 taken 1737 times.
✓ Branch 118 → 119 taken 8685 times.
✓ Branch 118 → 120 taken 1737 times.
✗ Branch 233 → 234 not taken.
✗ Branch 233 → 235 not taken.
13896 {"secondaryText", readable(QColor(palette["secondary"].toString())).name()}};
158 6948 }
159
160 1965 bool DesignTokens::validAccentColor(const QString &color) {
161
4/7
✓ Branch 2 → 3 taken 22 times.
✓ Branch 2 → 15 taken 1943 times.
✓ Branch 4 → 5 taken 22 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 22 times.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 39 not taken.
1987 static const QRegularExpression hex(QStringLiteral("^#[0-9a-fA-F]{6}$"));
162
1/1
✓ Branch 15 → 16 taken 1965 times.
1965 const auto trimmed = color.trimmed();
163
8/10
✓ Branch 17 → 18 taken 478 times.
✓ Branch 17 → 22 taken 1487 times.
✓ Branch 19 → 20 taken 478 times.
✓ Branch 20 → 21 taken 478 times.
✓ Branch 21 → 22 taken 464 times.
✓ Branch 21 → 23 taken 14 times.
✓ Branch 25 → 26 taken 478 times.
✓ Branch 25 → 27 taken 1487 times.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 42 not taken.
4408 return trimmed.isEmpty() || hex.match(trimmed).hasMatch();
164 1965 }
165 1239 bool DesignTokens::applyAccentColor(const QString &color) {
166
3/3
✓ Branch 2 → 3 taken 1239 times.
✓ Branch 3 → 4 taken 10 times.
✓ Branch 3 → 5 taken 1229 times.
1239 if (!validAccentColor(color)) return false;
167
2/2
✓ Branch 5 → 6 taken 1229 times.
✓ Branch 6 → 7 taken 1229 times.
1229 const auto canonical = color.trimmed().toLower();
168
2/2
✓ Branch 9 → 10 taken 774 times.
✓ Branch 9 → 11 taken 455 times.
1229 if (canonical == m_accentColor) return true;
169 455 m_accentColor = canonical;
170
1/1
✓ Branch 12 → 13 taken 455 times.
455 emit changed();
171 455 return true;
172 1229 }
173 1210 void DesignTokens::setAccentColor(const QString &color) { applyAccentColor(color); }
174 1015 void DesignTokens::setTheme(const QString &theme) {
175
9/10
✓ Branch 3 → 4 taken 972 times.
✓ Branch 3 → 10 taken 43 times.
✓ Branch 5 → 6 taken 860 times.
✓ Branch 5 → 11 taken 112 times.
✓ Branch 7 → 8 taken 771 times.
✓ Branch 7 → 11 taken 89 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 771 times.
✓ Branch 12 → 13 taken 43 times.
✓ Branch 12 → 14 taken 972 times.
1015 if (theme == m_theme || (theme != "light" && theme != "dark" && theme != "system")) return;
176 972 m_theme = theme;
177 972 emit changed();
178 }
179 1088 void DesignTokens::setFontScale(double scale) {
180
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1087 times.
1088 if (!std::isfinite(scale)) return;
181 1087 scale = qBound(1.0, scale, 2.0);
182
2/2
✓ Branch 7 → 8 taken 935 times.
✓ Branch 7 → 9 taken 152 times.
1087 if (qFuzzyCompare(scale, m_scale)) return;
183 152 m_scale = scale;
184 152 emit changed();
185 }
186 46 void DesignTokens::setReducedMotion(bool reduced) {
187 // The manual boolean remains the legacy entry point; it always pins an
188 // explicit policy (even when the effective value already matches) so
189 // persistence records the user's deliberate choice.
190
6/6
✓ Branch 2 → 3 taken 42 times.
✓ Branch 2 → 6 taken 4 times.
✓ Branch 9 → 10 taken 4 times.
✓ Branch 9 → 11 taken 42 times.
✓ Branch 11 → 12 taken 42 times.
✓ Branch 11 → 13 taken 4 times.
92 const QString policy = reduced ? QStringLiteral("reduced") : QStringLiteral("full");
191
5/6
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 17 taken 45 times.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 17 not taken.
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 20 taken 45 times.
46 if (policy == m_motionPolicy && reduced == m_reducedMotion) return;
192 45 m_motionPolicy = policy;
193 45 m_reducedMotion = reduced;
194
1/1
✓ Branch 21 → 22 taken 45 times.
45 emit changed();
195 46 }
196
197 782 void DesignTokens::applyMotionPolicy(const QString &policy) {
198
10/12
✓ Branch 6 → 7 taken 8 times.
✓ Branch 6 → 18 taken 774 times.
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 18 taken 6 times.
✓ Branch 25 → 26 taken 8 times.
✓ Branch 25 → 27 taken 774 times.
✓ Branch 27 → 28 taken 782 times.
✗ Branch 27 → 29 not taken.
✓ Branch 29 → 30 taken 782 times.
✗ Branch 29 → 31 not taken.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 33 taken 781 times.
2356 if (policy != QStringLiteral("system") && policy != QStringLiteral("reduced") &&
199
8/8
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 18 taken 1 time.
✓ Branch 19 → 20 taken 2 times.
✓ Branch 19 → 21 taken 780 times.
✓ Branch 21 → 22 taken 2 times.
✓ Branch 21 → 23 taken 780 times.
✓ Branch 23 → 24 taken 8 times.
✓ Branch 23 → 25 taken 774 times.
784 policy != QStringLiteral("full"))
200 1 return;
201 781 bool effective = m_reducedMotion;
202
2/2
✓ Branch 39 → 40 taken 6 times.
✓ Branch 39 → 41 taken 775 times.
781 if (policy == QStringLiteral("reduced")) effective = true;
203
2/2
✓ Branch 47 → 48 taken 1 time.
✓ Branch 47 → 49 taken 774 times.
775 else if (policy == QStringLiteral("full")) effective = false;
204
1/1
✓ Branch 49 → 50 taken 774 times.
774 else effective = m_motion.systemReducedMotion().value_or(false);
205
6/6
✓ Branch 53 → 54 taken 773 times.
✓ Branch 53 → 56 taken 8 times.
✓ Branch 54 → 55 taken 772 times.
✓ Branch 54 → 56 taken 1 time.
✓ Branch 57 → 58 taken 772 times.
✓ Branch 57 → 59 taken 9 times.
781 if (policy == m_motionPolicy && effective == m_reducedMotion) return;
206 9 m_motionPolicy = policy;
207 9 m_reducedMotion = effective;
208 9 emit changed();
209 }
210
211 5 void DesignTokens::refreshMotionFromSystem() {
212
2/2
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 3 times.
5 if (m_motionPolicy != QStringLiteral("system")) return;
213
1/1
✓ Branch 10 → 11 taken 3 times.
3 const auto system = m_motion.systemReducedMotion();
214 3 const bool reduced = system.value_or(false);
215
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 3 times.
3 if (reduced == m_reducedMotion) return;
216 3 m_reducedMotion = reduced;
217
1/1
✓ Branch 14 → 15 taken 3 times.
3 emit changed();
218 }
219 1532841 int DesignTokens::textSize() const { return qRound(13 * m_scale); }
220 463922 int DesignTokens::metadataSize() const { return qRound(12 * m_scale); }
221 // Typography scale (DESIGN.md §3 / decisions.md): 20 window brand, 18 view
222 // and reader headings, 15 secondary titles. QML must consume these tokens
223 // instead of repeating Math.round(N * fontScale).
224 6276 int DesignTokens::sectionSize() const { return qRound(15 * m_scale); }
225 27617 int DesignTokens::headingSize() const { return qRound(18 * m_scale); }
226 1151 int DesignTokens::titleSize() const { return qRound(20 * m_scale); }
227 815656 int DesignTokens::rowHeight() const {
228
1/1
✓ Branch 2 → 3 taken 815656 times.
815656 QFont font = QGuiApplication::font();
229
1/1
✓ Branch 4 → 5 taken 815656 times.
815656 font.setPixelSize(textSize());
230
2/2
✓ Branch 5 → 6 taken 815656 times.
✓ Branch 6 → 7 taken 815656 times.
1631312 return qMax(20, QFontMetrics(font).height() + 4);
231 815656 }
232
1/1
✓ Branch 2 → 3 taken 630381 times.
630381 int DesignTokens::controlHeight() const { return qMax(28, rowHeight() + 8); }
233 32022 QString DesignTokens::monoFamily() const {
234 24 static const QString family = [] {
235
1/1
✓ Branch 2 → 3 taken 24 times.
24 const QStringList installed = QFontDatabase::families();
236
2/2
✓ Branch 18 → 13 taken 48 times.
✓ Branch 18 → 19 taken 24 times.
192 for (const QString &candidate : {QStringLiteral("JetBrains Mono"), QStringLiteral("Cascadia Code")})
237
3/4
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 17 taken 48 times.
✓ Branch 21 → 22 taken 48 times.
✓ Branch 21 → 24 taken 24 times.
120 if (installed.contains(candidate)) return candidate;
238
2/2
✓ Branch 25 → 27 taken 24 times.
✓ Branch 27 → 28 taken 24 times.
24 return QFontDatabase::systemFont(QFontDatabase::FixedFont).family();
239
4/7
✓ Branch 2 → 3 taken 24 times.
✓ Branch 2 → 9 taken 31998 times.
✓ Branch 4 → 5 taken 24 times.
✗ Branch 4 → 9 not taken.
✓ Branch 5 → 6 taken 24 times.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
32046 }();
240 32022 return family;
241 }
242
2/2
✓ Branch 2 → 3 taken 9 times.
✓ Branch 3 → 4 taken 9 times.
9 int DesignTokens::wheelScrollLines() const { return qMax(0, QGuiApplication::styleHints()->wheelScrollLines()); }
243