ADR-013: Brand Token Runtime JSON vs. Compile-Time Source Generator¶
Status: Accepted (2026-05-12) — backfilled to document a decision already shipped in PR #4.
Renumbered from ADR-009, moved from
sigil-docs/architecture/(register row R27).sigil-docs/held a second, stale ADR tree that collided with two numbers already in use underdocs/architecture/(ADR-009 = update-manifest signature, ADR-010 = delta-update deferral). Content below is unchanged from the original except for this notice and the number in the title.
Context¶
Plan _4 (Installer UI) originally specified a Roslyn source generator project — SigilBuild.Installer.BrandGenerator — that would read a brand-tokens.json at build time and emit a BrandTokens.g.cs class into the installer host. The intent was to give the installer binary a strongly-typed, compile-time-validated set of brand constants with zero runtime JSON parsing overhead.
During Sprint 5b implementation, the shipped path instead loads brand-tokens.json at runtime via BrandTokens.LoadOrDefault, using a System.Text.Json source-gen context (BrandTokensJsonContext) for AOT-safe deserialization.
Decision¶
Use runtime JSON loading via BrandTokens.LoadOrDefault / BrandTokensJsonContext. The empty SigilBuild.Installer.BrandGenerator project is preserved as a placeholder but emits no code.
Rationale¶
Build-graph complexity. A Roslyn analyzer project requires careful MSBuild item-ordering: the host project must declare the generator as an Analyzer reference, the generator csproj must target netstandard2.0, and outputs must be available before the host's compilation phase. During Sprint 5b this created ordering issues between the installer host and the generator that were non-trivial to resolve without restructuring the solution layout.
No runtime cost saving. The brand-tokens JSON is small (< 2 KB) and parsed exactly once at startup. The STJ source-gen context makes the parse allocation-efficient and fully AOT-safe. The source generator would not have improved cold-start or memory use in any measurable way.
Brand-owner flexibility. With runtime loading, a brand owner can ship an updated brand-tokens.json alongside a new installer package without recompiling the host binary. The compile-time approach would have required a full rebuild for every brand update.
Status of this bullet: superseded by T7/T16b. The decision stands; this rationale no longer holds. There is no shippable sidecar. Brand tokens travel inside the
SIGIL_BLOB_V1resource of the signedSetup.exe— the code says so explicitly ("noBrandTokens.g.jsonsidecar for a stamped installer",src/SigilBuild.Installer.Host/Branding/BrandTokens.cs), and a sidecar a third party could swap beside a signed installer would be a trust hole rather than a feature. The other three arguments for runtime JSON parsing over source generation are unaffected, and the choice they support is unchanged. See ADR — the MSIX companion question.
AOT Impact¶
None. BrandTokensJsonContext is a JsonSerializerContext subclass generated by System.Text.Json source generators, which is fully AOT-compatible. No reflection is involved in the runtime path.
Consequences¶
- No compile-time WCAG contrast validation in the installer host itself. Instead,
BrandTokenEmitterinSigilBuild.Packagingruns the contrast check atsigil packtime and blocks the build if any color pair fails WCAG-AA. - The
SigilBuild.Installer.BrandGeneratorproject compiles (it is a valid analyzer shell) but contributes no generated code to any consumer.
Reverting¶
If a future requirement demands compile-time token validation inside the installer host (for example, embedding brand colors as IL constants to avoid startup I/O), the generator placeholder can be implemented without breaking callers — BrandTokens.LoadOrDefault can be replaced with the generated static class under the same public surface.