Skip to content

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 under docs/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_V1 resource of the signed Setup.exe — the code says so explicitly ("no BrandTokens.g.json sidecar 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, BrandTokenEmitter in SigilBuild.Packaging runs the contrast check at sigil pack time and blocks the build if any color pair fails WCAG-AA.
  • The SigilBuild.Installer.BrandGenerator project 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.