Skip to content

ADR-011: Consolidated Report CSS Source

Status

Superseded by ADR-017 (2026-10-05). The report CSS, the theme and the templates it describes were removed with the HTML stack. Originally accepted 2026-04-26.

Context

ADR-005 established programmatic HTML rendering via Python + BeautifulSoup, and the federated theme work (PRD-federated-html) introduced ui_theme.py as the single source of truth for theme variables (colors). The variables were dynamically generated by generate_theme_css() and string-replaced into templates/universal_report_template.html at render time.

However, the component styles (layout, cards, tables, badges, sections, crew-specific decoration) were still scattered across:

  • templates/universal_report_template.html — ~300 lines of inline <style> covering body/container/headings/tables/badges/footer
  • 19 renderer files in src/epic_news/utils/html/template_renderers/ — each with its own _add_styles() method emitting an inline <style> block of 50–390 lines via BeautifulSoup
  • 2 orphaned templates/css/*.css files referenced nowhere

This produced ~2700 lines of CSS spread across 21 files. Pain points:

  • Changing a single visual rule (e.g., card padding) required touching multiple files.
  • Some renderers hardcoded local --background-light: #fff etc., bypassing the federated theme.
  • Renderers redefined font-family stacks, breaking typography consistency.
  • Some classes referenced var(--accent-color) without that variable being defined anywhere → silently rendered as transparent.
  • No support for @media print rules — generated reports printed on far too many pages.

Decision

Consolidate all component CSS into a single static file inlined at render time:

  • templates/css/report.css is the single source for component styles (~1100 lines, namespaced by renderer root class).
  • TemplateManager.render_report() reads it once at process start (functools.cache) and concatenates with the dynamic theme block produced by generate_theme_css(). Both are injected into <style id="theme-styles"> in universal_report_template.html.
  • Renderers emit only semantic HTML with class names — no <style> tags, no _add_styles() methods.
  • ui_theme.py keeps owning dynamic values (light/dark theme vars, typography vars, @media print rules). It also exports --font-family-base (Arial Nova Light + system fallbacks) and a print block that switches body to 9pt and protects page breaks.

Consequences

Positive

  • One file to edit for any visual change. CSS variables flow consistently into every report.
  • Reports remain self-contained for email distribution (CSS inlined, no <link> to external stylesheet).
  • Print output is now compact (9pt Arial Nova Light) with sensible page-break protections.
  • ~2200 lines of duplicated CSS removed from renderer Python files; renderers shrink to pure HTML emission.
  • Typography typos and missing CSS variables (--accent-color was undefined) caught and fixed in passing.
  • Renderer rendering tests now reflect the unified CSS — easier to spot styling regressions.

Negative / Trade-offs

  • The consolidated file is large (~1100 lines). We accept this because it's all in one place and functools.cache ensures it's read once per process.
  • All renderer rendering snapshots (tests/rendering/test_template_manager_rendering/*.html) had to be regenerated — diff is ~2200+ lines per snapshot but deterministic.
  • The legacy templates/css/dashboard.css was deleted along with the entire dashboard HTML pipeline (dashboard_template.html, dashboard_generator.py) — that observability surface was unused.
  • templates/css/menu_report.css is still consumed by the menu_designer crew via menu_utils.py::embed_css_in_html. A follow-up will fold it into report.css under a body-class namespace; tracked separately.

Implementation Notes

  • Loader: _load_static_css() in src/epic_news/utils/html/template_manager.py (uses functools.cache).
  • Placeholder injection: template now contains {{ theme_css_vars }} and {{ static_css }} inside the same <style> block.
  • Class namespacing convention: .{crew-name}-report .component { … } — keeps cross-crew styles isolated even though they share the global file.
  • --font-family-base is the global default; only the poem renderer overrides it for Georgia (poetic feel).

References

  • ADR-005: Deterministic HTML Rendering Over LLM-Based Generation
  • PRD: docs/prd/PRD-federated-html.md
  • PR: #83 (CSS consolidation + Arial Nova Light @ 9pt print)
  • PR: #85 (RSS weekly renderer fix exposed by the consolidation work)