All files / generators propositions-content.ts

100% Statements 4/4
100% Branches 5/5
100% Functions 1/1
100% Lines 4/4

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96                                                                                            33x 33x                           33x             33x                                                      
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
 
/**
 * @module Generators/PropositionsContent
 * @description Pure function for building propositions article HTML with
 * localized strings and pre-sanitized MCP data.
 */
 
import { escapeHTML } from '../utils/file-utils.js';
import { getLocalizedString, EDITORIAL_STRINGS } from '../constants/languages.js';
import type { PropositionsStrings } from '../types/index.js';
 
/** Structured pipeline data returned from MCP before language-specific rendering */
export interface PipelineData {
  healthScore: number;
  throughput: number;
  procRowsHtml: string;
}
 
/**
 * Build propositions article HTML content with localized strings.
 *
 * **Security contract**: `proposalsHtml`, `procedureHtml`, and
 * `pipelineData.procRowsHtml` MUST be pre-sanitized HTML — all external
 * (MCP-sourced) values must have been passed through `escapeHTML()` before
 * being interpolated into these strings.  The fetch helpers
 * (`fetchProposalsFromMCP`, `fetchPipelineFromMCP`,
 * `fetchProcedureStatusFromMCP`) fulfil this contract; callers must do the
 * same if they construct these arguments independently.
 *
 * @param proposalsHtml - Pre-sanitized HTML for proposals list section
 * @param pipelineData - Structured pipeline data from MCP (null when unavailable);
 *   `pipelineData.procRowsHtml` must be pre-sanitized HTML
 * @param procedureHtml - Pre-sanitized HTML for tracked procedure status section (may be empty)
 * @param strings - Localized string set for the target language
 * @param lang - Language code for editorial string headings (default: 'en')
 * @returns Full article HTML content string
 */
export function buildPropositionsContent(
  proposalsHtml: string,
  pipelineData: PipelineData | null,
  procedureHtml: string,
  strings: PropositionsStrings,
  lang = 'en'
): string {
  const editorial = getLocalizedString(EDITORIAL_STRINGS, lang);
  const pipelineHtml = pipelineData
    ? `
    <div class="pipeline-metrics">
      <div class="metric" aria-label="${escapeHTML(strings.pipelineHealthLabel)}">
        <span class="metric-label">${escapeHTML(strings.pipelineHealthLabel)}</span>
        <span class="metric-value">${escapeHTML(String(Math.round(pipelineData.healthScore * 100)))}%</span>
      </div>
      <div class="metric" aria-label="${escapeHTML(strings.throughputRateLabel)}">
        <span class="metric-label">${escapeHTML(strings.throughputRateLabel)}</span>
        <span class="metric-value">${escapeHTML(String(pipelineData.throughput))}</span>
      </div>
    </div>
    ${pipelineData.procRowsHtml}`
    : '';
  const procedureSection = procedureHtml
    ? `
          <section class="procedure-status">
            <h2>${escapeHTML(strings.procedureHeading)}</h2>
            ${procedureHtml}
          </section>`
    : '';
  return `
        <div class="article-content">
          <section class="lede">
            <p>${escapeHTML(strings.lede)}</p>
          </section>
 
          <section class="proposals-list">
            <h2>${escapeHTML(strings.proposalsHeading)}</h2>
            ${proposalsHtml}
          </section>
 
          <section class="pipeline-status">
            <h2>${escapeHTML(strings.pipelineHeading)}</h2>
            ${pipelineHtml}
          </section>
          ${procedureSection}
          <section class="analysis">
            <h2>${escapeHTML(strings.analysisHeading)}</h2>
            <p>${escapeHTML(strings.analysis)}</p>
          </section>
          <section class="why-this-matters">
            <h2>${escapeHTML(editorial.whyThisMatters)}</h2>
            <p>${escapeHTML(strings.whyThisMatters)}</p>
          </section>
        </div>
      `;
}