Skip to content

ADR-0002: UI Architecture — Server-Rendered Read Interface

Status: Accepted (2026-05-19) Supersedes:Superseded by:


Context

The v0.4 spec assumed a Google Sheets frontend — Node app writes scoring results into a Sheets dashboard via API, weekly. By May 2026 the data pipeline (VM, schema, scoring engine, three scrapers, eight pilot institutions running end-to-end) is mature, but no read surface has been built. The placeholder at esg-screen.org is a gated stub.

This ADR records the move from "Sheets is the frontend" to "web UI is the frontend." Driven by two observations: (a) Sheets was always a workaround for not wanting to build a frontend, and the Cloudflare Access / Node server scaffolding was already in place to host one; (b) the team's screening workflow needs single-name deep dives, filtered views, and a watchlist surface that Sheets handles poorly.

Decision

Build a server-rendered web read interface, hosted at esg-screen.org, gated by the existing Cloudflare Access OTP + 5-email allow list.

Stack: Node + Express (already in repo) + EJS or Handlebars templates + plain HTML/CSS. No SPA, no build step, no client-side framework. HTMX may be introduced later for interactive partial updates (rules-table filtering, watchlist refresh) but is not required for v1.

Pages, v1: 1. Institution index (/) — list of all pilot institutions with score, coverage, sector, red flags. Filterable and sortable. Lands here first. 2. Institution detail (/institution/:lei) — full per-institution page. Designed in mock institution-detail-mock-v4.html (see References). 3. Methodology (/methodology) — source register, rule catalogue, scoring explainer. Generated from DB joins, not static. See ADR-0003. 4. Runs (/runs) — list of scrape runs with status, durations, error counts. Internal-only. 5. Watchlist (/watchlist) — cross-institution surface of findings not captured in score. Populates as controversy scrapers land.

Out of scope for v1: comparison view, exception-hunting filters, client-facing summaries, PDF export styling, write actions of any kind. v1 is read-only.

Detail page structure (canonical, see mock for visual reference): - Header. Breadcrumb, institution name, LEI/GICS/domicile/section metadata, run stamp. - Red flags band. Manual-at-intake flags (fossil financing, weapons manufacturing) per v0.4. Red when exposed, green when clear (see ADR-0004 for palette). - Hero panel. Two equally-weighted numbers: ESG composite (coverage-weighted per ADR-0001) and coverage %. Both displayed as percentages, both RAG colour-coded. Three secondary metrics underneath: average confidence, peer rank, delta from previous run. Coverage explainer paragraph below. - Pillar breakdown. Three boxes (E / S / G) showing score, weight, coverage, covered rule count. - Rule evaluation. Filter pills (All / E / S / G / Covered only / Uncovered only) above a grouped table. Rules grouped by theme per ADR-0005. - Sidebar: Watchlist, Major Shareholders, Peer Group, Score History, Export actions. - Financial Institutions Supplement. Visually-distinct second zone (warmer background, divider band). Triangulated composite (ESG × credit × returns) for financial institutions only. Currently shows ESG + "awaiting" placeholders for credit and returns dimensions.

Consequences

Positive. - Cheap to build. Server-rendered HTML + small JS for filtering is a 2–3 cycle build, not a 6–8 cycle SPA project. - No build step → CC-on-VM sessions can edit templates and see results immediately. No webpack, no node_modules bloat beyond what's already there. - Cloudflare Access gating already in place — no auth work needed for v1. - Methodology page becomes a queryable artefact of system state (see ADR-0003) — useful for both internal debugging and client-facing defensibility. - "Methodology" link in top nav makes source transparency structural, not buried.

Negative. - Limited interactivity. Filtering, sorting, comparison views require either page reloads or HTMX-style partial updates. Acceptable for v1 with <10 users; revisit if v2 needs richer interaction. - No mobile-first design. Mock is desktop-only. Acceptable — screening work happens at desks. - No write surface in v1 means manual intake (red flags, shareholder data) is still done outside the UI. Tolerable for now.

Tech stack constraints. - No browser storage (localStorage, sessionStorage). Filtering state lives in URL query params, surviving page reloads and bookmarkable. - Tabular numeric figures everywhere via font-variant-numeric: tabular-nums. Inter for sans, JetBrains Mono for codes/labels. No serifs, no italics anywhere — both proved visually noisy in mock iteration. - Cream paper background (#f4f1ea) plus minimal colour palette. RAG accents only used for status (see ADR-0004), never decoratively.

Generated-from-DB principle. Methodology page, rule catalogue, source register, coverage matrix are all DB joins, not hand-written content. Adding a new scraper updates the Methodology page automatically. Source breakage is visible because last-run timestamps and error states are surfaced (see ADR-0003).

Alternatives considered

  • Stay with Google Sheets as frontend. Cheap to start but expensive forever: Sheets cannot do per-institution deep dives well, the API is rate-limited, and styling a "client-facing summary" tab is much harder than rendering server-side HTML.
  • SPA (React/Vue/Svelte). Overkill for <10 internal users with a read-only surface. Adds build complexity, client-side state management, hydration concerns. No payback at this scale.
  • HTMX-first. Considered. Probably the right choice for v2 once filtering and watchlist interactions get richer. Deferred to keep v1 build simple.
  • Streamlit / similar Python data-app frameworks. Adds a second language to the stack (current stack is Node-only). Visual control is limited. Rejected.

References

  • Mock file: institution-detail-mock-v4.html (attached to 2026-05-19 chat session, also in #esg-screening thread)
  • ADR-0001 (coverage-weighted scoring) — drives the hero panel shape
  • ADR-0003 (source register and Methodology page)
  • ADR-0004 (RAG thresholds)
  • ADR-0005 (rule grouping)
  • Original v0.4 spec, "Frontend" section