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/*.cssfiles 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: #fffetc., 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 printrules — 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.cssis 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 bygenerate_theme_css(). Both are injected into<style id="theme-styles">inuniversal_report_template.html.- Renderers emit only semantic HTML with class names — no
<style>tags, no_add_styles()methods. ui_theme.pykeeps owning dynamic values (light/dark theme vars, typography vars,@media printrules). 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-colorwas 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.cacheensures 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.csswas deleted along with the entire dashboard HTML pipeline (dashboard_template.html,dashboard_generator.py) — that observability surface was unused. templates/css/menu_report.cssis still consumed by the menu_designer crew viamenu_utils.py::embed_css_in_html. A follow-up will fold it intoreport.cssunder a body-class namespace; tracked separately.
Implementation Notes¶
- Loader:
_load_static_css()insrc/epic_news/utils/html/template_manager.py(usesfunctools.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-baseis the global default; only the poem renderer overrides it forGeorgia(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)