Acceptance Criteria
@refrakt-md/content exports SSR helpers that adapters consume to emit the right <html> attributes:htmlTintAttributes(cascade) — returns data-theme="dark" etc., empty string for the default auto/unlocked cascadecolorSchemeMetaContent(cascade) — returns the <meta name="color-scheme"> content ('dark' / 'light' when locked, 'light dark' otherwise)prePaintScript() — returns the canonical anti-FOIT script (IIFE, ~250 bytes, reads rf-theme localStorage, falls back to prefers-color-scheme, no-ops on data-tint-lock="true")
- Pre-paint script reads
data-tint-lock; if locked, does nothing; if unlocked, applies saved preference from localStorage, falls back to prefers-color-scheme - Pre-paint script is small and inline-friendly — sub-1KB (~250 bytes)
- Unit tests verify HTML attribute output and
<meta> content for each combination of (tint, tintMode, locked) — 17 new tests in tint-ssr.test.ts - Adapter integration that actually wires the SSR helpers onto
<html> for the refrakt site (deferred to WORK-215 in Chunk 10 — the site-adoption work item is the natural home for the hooks.server.ts that consumes these helpers) - No flash of incorrect theme verification (deferred to WORK-215 — meaningful only once the adapter wiring lands)
Approach
Most of this is straightforward — pass the resolved tuple from WORK-212 into the layout/renderer and produce the right HTML.
The pre-paint script is the most subtle piece. The contract from WORK-211's toggle:
<script>
(function() {
if (document.documentElement.dataset.tintLock === 'true') return;
var saved = localStorage.getItem('rf-theme');
var resolved = saved && saved !== 'auto'
? saved
: (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', resolved);
})();
</script>
This script and WORK-211's toggle need to use the same localStorage key (rf-theme here, but pick the canonical name during implementation and use it consistently).
For other adapters (Astro, Next, Eleventy, plain HTML) — the same <html> attributes and the same pre-paint script need to be emitted by their respective render pipelines. Likely a single shared utility in @refrakt-md/content that all adapters call.
Dependencies
- WORK-212 — cascade resolution produces the tuple this consumes.
- WORK-213 — frontmatter schema must accept the fields.
- WORK-189 —
theme.colorScheme field already exists at site level (this work generalises its emit to per-page).
References
- SPEC-052 — "SSR & Rendering" section
- WORK-211 — toggle that pairs with this pre-paint script
Resolution
Completed: 2026-05-19
Shipped: @refrakt-md/content exports htmlTintAttributes, colorSchemeMetaContent, and prePaintScript from tint-ssr.ts. The pre-paint script is ~250 bytes (IIFE), reads data-tint-lock and no-ops on locked pages, otherwise applies localStorage.rf-theme with a prefers-color-scheme fallback. 17 unit tests in tint-ssr.test.ts cover the (tint, tintMode, locked) combination matrix. Adapter wiring + FOIT spot-check belong to WORK-215, which has also shipped.