Skip to content

Externalize news article CSS, add quality framework, and implement workflow coordination#156

Draft
Copilot wants to merge 11 commits intomainfrom
copilot/improve-multi-language-sync
Draft

Externalize news article CSS, add quality framework, and implement workflow coordination#156
Copilot wants to merge 11 commits intomainfrom
copilot/improve-multi-language-sync

Conversation

Copy link
Contributor

Copilot AI commented Feb 14, 2026

CSS Architecture Refactor

Eliminated 660 lines of embedded <style> tags from each generated article. All styles now in styles.css under .news-article namespace for better caching and maintainability.

Added editorial tagline with proper lang="en" attribute for non-English pages:

<div class="site-tagline" lang="en">Latest news and analysis from Sweden's Riksdag...</div>

Quality Framework

scripts/article-quality-enhancer.js - Economist-style quality validation:

  • Analytical depth scoring (0.0-1.0, min 0.6)
  • Party perspective counting (8 Swedish parties, min 4)
  • Cross-reference detection (Riksdag document IDs, min 3)
  • "Why This Matters" section validation
  • Historical context and international comparison checks

Score calculation weights: analytical depth 30%, party perspectives 25%, cross-references 25%, other 20%.

Workflow Coordination

scripts/workflow-state-coordinator.js - Deduplication and caching:

  • MCP query caching with per-entry TTL (default 2h)
  • Text similarity-based deduplication (70% threshold)
  • Recent article tracking (6h window)
  • Workflow execution logging

Language Index Filtering

News indexes now show language-specific articles only:

// Generate each index with only its language articles
const languageArticles = articlesByLang[langKey] || [];
const html = generateIndexHTML(langKey, languageArticles, articlesByLang);

Result: news/index.html shows 10 EN articles, news/index_sv.html shows 10 SV articles, etc.

Additional Fixes

  • RTL direction handling: removed paragraph-level dir="ltr" for Arabic/Hebrew
  • Client-side language badge reuse: emit server constants to avoid duplication
  • Performance: compute getAllArticlesWithLanguageInfo() once before loop
  • Accessibility: proper lang attributes, focus states for keyboard navigation
  • Test coverage: 38 tests for multi-language, 25 tests for workflow coordination
Original prompt

This section details on the original issue you should resolve

<issue_title>Improve news-realtime-monitor.md: Multi-language synchronization and quality enhancement</issue_title>
<issue_description>## 📋 Issue Type
Feature Enhancement / Bug Fix

🎯 Objective

Improve the news-realtime-monitor.md agentic workflow to ensure comprehensive multi-language synchronization, proper news index updates, and enhanced Economist-style quality standards for real-time Riksdag/Regering coverage.

📊 Current State

Workflow Analysis

File: .github/workflows/news-realtime-monitor.md
Schedule: Twice daily (10:00 UTC, 14:00 UTC) on weekdays
Model: claude-opus-4.6
MCP Server: riksdag-regering (https://riksdag-regering-ai.onrender.com/mcp)
Timeout: 20 minutes

Current Issues Identified

  1. Multi-Language Index Synchronization

    • Problem: News index files (news/index_*.html) for non-generated languages are NOT updated with new articles
    • Example: If English/Swedish articles are generated, news/index_fr.html won't contain links to these articles
    • Impact: French readers cannot discover English/Swedish breaking news
    • Root Cause: Language filtering in generate-news-indexes.js excludes articles based on filename suffix
  2. Quality Framework Missing

    • No systematic quality scoring (analytical depth, source quality, balance)
    • Missing "Why This Matters" section requirement
    • No minimum party perspective count (should be 4+)
    • No cross-referencing validation (should be 3+ sources)
  3. Workflow Coordination Gap

    • No state file (news/metadata/workflow-state.json) to track recent coverage
    • Risk of duplicate articles between realtime-monitor and evening-analysis
    • No MCP query caching (2-hour TTL recommended)
  4. Test Coverage

    • Missing tests for realtime-monitor-specific logic
    • No validation of breaking news threshold criteria
    • No tests for Playwright validation integration

Measured Metrics

  • News Index Files: 14 language files exist
  • Recent Articles: 22 articles in news/ directory (Feb 10-14)
  • Generated Languages: Primarily EN/SV, missing FR/DE/ES/NL in recent runs
  • Breaking News: 0 breaking articles generated in last 4 days
  • Quality Scores: Not implemented (no scoring system)

🚀 Desired State

Target Outcomes

  1. Cross-Language Article Discovery

    • ✅ All 14 news/index_*.html files show ALL articles regardless of language
    • ✅ Language badges indicate article availability ("Available in: EN, SV")
    • ✅ Readers can discover relevant content across languages
  2. Economist-Style Quality Framework

    • ✅ Every article has quality score ≥ 0.75
    • ✅ Analytical depth score ≥ 0.6
    • ✅ "Why This Matters" section (100-150 words)
    • ✅ 4+ party perspectives represented
    • ✅ 3+ cross-referenced sources with document IDs
    • ✅ Historical context included
  3. Workflow State Coordination

    • ✅ news/metadata/workflow-state.json tracks recent queries/articles
    • ✅ 2-hour MCP query caching prevents duplicate API calls
    • ✅ Similarity-based deduplication (>70% similar articles prevented)
  4. Comprehensive Testing

    • ✅ tests/news-realtime-monitor.test.js with 20+ test cases
    • ✅ Breaking news criteria validation
    • ✅ Multi-language index synchronization tests
    • ✅ Quality framework scoring tests

🔧 Implementation Approach

Phase 1: Multi-Language Index Fix (Priority: HIGH)

Modify: scripts/generate-news-indexes.js

// BEFORE (incorrect - filters by language)
const enArticles = allArticles.filter(a => a.file.endsWith('-en.html'));
const svArticles = allArticles.filter(a => a.file.endsWith('-sv.html'));

// AFTER (correct - show all articles in all indexes)
const allArticlesForIndex = allArticles.map(article => ({
  ...article,
  availableLanguages: detectAvailableLanguages(article.slug, allArticles),
  badge: article.language // Show which language this article is in
}));

// Generate index for each language showing ALL articles
for (const lang of LANGUAGES) {
  generateIndexHTML(lang, allArticlesForIndex);
}

Key Changes:

  • Remove language filtering in article gathering
  • Add availableLanguages detection per article slug
  • Show language badges for each article
  • Add "Available in: EN, SV, FR" indicator

Phase 2: Quality Framework Implementation

Create: scripts/article-quality-enhancer.js

Features:

  • Quality score calculation (0.0-1.0)
  • Analytical depth assessment
  • Source quality validation
  • Party perspective counting
  • Historical context detection

Integration:

// In news-realtime-monitor.md, after article generation
const qualityResult = await enhanceArticleQuality(articlePath, {
  minQualityScore: 0.75,
  minAnalyticalDepth: 0.6,
  minPartySources: 4,
  minCrossReferences: 3,
  requireWhyThisMatters: true
});

if (!qualityResult.passed) {
  console.warn('Quality check failed:', qua...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes Hack23/riksdagsmonitor#155

<!-- START COPILOT CODING AGENT TIPS -->
---

 Let Copilot coding agent [set things up for you](https://github.com/Hack23/riksdagsmonitor/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)  coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits February 14, 2026 00:21
…ntified

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
… badges

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
const f = lang.filters;
const filename = langKey === 'en' ? 'index.html' : `index_${langKey === 'no' ? 'no' : langKey}.html`;
const mainIndex = langKey === 'en' ? 'index.html' : `index_${langKey === 'no' ? 'no' : langKey}.html`;
const isRTL = ['ar', 'he'].includes(langKey);
Copilot AI and others added 2 commits February 14, 2026 00:28
Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
…eduplication

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { WorkflowStateCoordinator, MCP_CACHE_TTL_SECONDS, SIMILARITY_THRESHOLD } from '../scripts/workflow-state-coordinator.js';
Copilot AI changed the title [WIP] Enhance multi-language synchronization and quality for news monitor Multi-language news index sync, quality framework, and workflow coordination Feb 14, 2026
Copilot AI requested a review from pethers February 14, 2026 00:41
Copilot AI and others added 2 commits February 14, 2026 00:54
Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copilot AI changed the title Multi-language news index sync, quality framework, and workflow coordination Move news article CSS to external stylesheet and add editorial tagline Feb 14, 2026
@pethers pethers requested a review from Copilot February 14, 2026 01:03
@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors news article CSS architecture from embedded styles (660 lines per article) to a centralized external stylesheet, adds editorial branding with a tagline, implements multi-language discovery features, and introduces quality framework and workflow coordination tools for the news generation system.

Changes:

  • Moved ~350 lines of article CSS from embedded <style> tags to external styles.css under .news-article namespace
  • Added "The Economist-style" editorial tagline to article headers
  • Implemented cross-language article discovery with language badges and "Available in" indicators across all 14 supported languages
  • Created workflow state coordinator for MCP query caching and article deduplication
  • Added article quality enhancer with analytical depth scoring, party perspective counting, and cross-reference validation
  • Added comprehensive test suites (362 lines for workflow coordination, 443 lines for realtime monitor quality)

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
styles.css Added 350 lines of .news-article styles (lines 8703-9049) for typography, event calendars, watch sections, responsive design, print optimization, and editorial tagline
scripts/article-template.js Removed generateArticleCSS() call, added HTML comment clarifying external CSS use, added site tagline div with editorial branding
scripts/generate-news-indexes.js Added getAllArticlesWithLanguageInfo(), generateLanguageBadge(), and generateAvailableLanguages() functions for cross-language discovery with 14-language support
news/index_*.html Updated all 14 language index files with lang and availableLanguages metadata, language badge rendering, and multi-language discovery UI
scripts/workflow-state-coordinator.js New 338-line module for MCP caching (2-hour TTL), similarity-based deduplication (70% threshold), and workflow coordination
scripts/article-quality-enhancer.js New 369-line module implementing Economist-style quality framework with analytical depth (0-1), party perspectives (4+), cross-references (3+), and quality scoring (0.75+ target)
tests/workflow-state-coordinator.test.js 362 lines of tests covering state management, MCP caching, duplicate detection, similarity calculations (25 test cases)
tests/news-realtime-monitor.test.js 443 lines of tests covering multi-language sync, quality metrics, party counting, cross-referencing (40+ test cases)

const availableText = '可用语言';
const availableBadges = availableLangs.map(l => {
const f = {en:'🇬🇧',sv:'🇸🇪',da:'🇩🇰',no:'🇳🇴',fi:'🇫🇮',de:'🇩🇪',fr:'🇫🇷',es:'🇪🇸',nl:'🇳🇱',ar:'🇸🇦',he:'🇮🇱',ja:'🇯🇵',ko:'🇰🇷',zh:'🇨🇳'}[l] || '🌐';
return `<span class="lang-badge-sm"><span aria-hidden="true">${f}</span> ${l.toUpperCase()}</span>`;
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CSS class lang-badge-sm is used in the JavaScript but is not defined in styles.css. This will result in unstyled language badges in the "Available in" section. You should either:

  1. Add .lang-badge-sm styles to styles.css, or
  2. Reuse the existing .language-badge class that's already defined for .news-page
Suggested change
return `<span class="lang-badge-sm"><span aria-hidden="true">${f}</span> ${l.toUpperCase()}</span>`;
return `<span class="language-badge"><span aria-hidden="true">${f}</span> ${l.toUpperCase()}</span>`;

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added .lang-badge-sm and .available-languages CSS styles to styles.css in commit a2f376d. The language badges will now display with proper styling (green theme, borders, appropriate sizing) consistent with the project's design system.

@pethers
Copy link
Member

pethers commented Feb 14, 2026

@copilot apply changes based on the comments in this thread

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 9 comments.

Comment on lines 97 to 101
// Clean MCP cache (2-hour TTL)
Object.keys(this.state.mcpQueryCache).forEach(key => {
const entry = this.state.mcpQueryCache[key];
const entryTime = new Date(entry.timestamp).getTime();
if (now - entryTime > MCP_CACHE_TTL_SECONDS * 1000) {
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanupExpiredEntries() expires MCP cache entries using the constant MCP_CACHE_TTL_SECONDS rather than each entry’s stored ttl. Since cacheMCPQuery() allows custom TTLs, entries with TTL > 2h can be deleted too early. Use entry.ttl (with a sane default) when deciding expiration here.

Suggested change
// Clean MCP cache (2-hour TTL)
Object.keys(this.state.mcpQueryCache).forEach(key => {
const entry = this.state.mcpQueryCache[key];
const entryTime = new Date(entry.timestamp).getTime();
if (now - entryTime > MCP_CACHE_TTL_SECONDS * 1000) {
// Clean MCP cache using per-entry TTL (default: 2 hours)
Object.keys(this.state.mcpQueryCache).forEach(key => {
const entry = this.state.mcpQueryCache[key];
const entryTime = new Date(entry.timestamp).getTime();
const effectiveTtlSeconds =
typeof entry.ttl === 'number' && entry.ttl > 0
? entry.ttl
: MCP_CACHE_TTL_SECONDS;
if (now - entryTime > effectiveTtlSeconds * 1000) {

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 849b756. Now uses per-entry TTL with fallback to default constant:

const effectiveTtlSeconds =
  typeof entry.ttl === 'number' && entry.ttl > 0
    ? entry.ttl
    : MCP_CACHE_TTL_SECONDS;

Comment on lines 175 to 197
/**
* Check if article is duplicate based on similarity
*
* @param {string} title - Article title
* @param {string[]} topics - Article topics
* @param {string[]} sources - Article sources
* @returns {Object} { isDuplicate: boolean, matchedArticle: Object|null, similarityScore: number }
*/
async checkDuplicateArticle(title, topics = [], sources = []) {
this.cleanupExpiredEntries();

let maxSimilarity = 0;
let matchedArticle = null;

for (const recentArticle of this.state.recentArticles) {
const similarity = this.calculateSimilarity(
title,
topics,
sources,
recentArticle.title,
recentArticle.topics,
recentArticle.mcpQueries
);
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkDuplicateArticle() and calculateSimilarity() refer to the third parameter as sources, but the implementation compares it against recentArticle.mcpQueries. This API/Doc mismatch makes it easy to pass the wrong data and get incorrect duplicate detection. Align naming and docs (e.g., rename to mcpQueries everywhere, or add a separate sources field in recentArticles).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 849b756. Renamed all instances from sources to mcpQueries for consistency:

  • checkDuplicateArticle(title, topics, mcpQueries)
  • calculateSimilarity(..., mcpQueries1, ..., mcpQueries2)
  • Updated JSDoc comments to match implementation

No embedded CSS needed - promotes consistency and maintainability. -->
<article class="news-article">
<header class="article-header">
<div class="site-tagline">Latest news and analysis from Sweden's Riksdag. The Economist-style political journalism covering parliament, government, and agencies with systematic transparency.</div>
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In non-English article pages, the new tagline is still an English sentence but isn’t marked as such. For accessibility (screen readers) and correct language metadata, consider adding lang="en" to the tagline element or providing per-language translations.

Suggested change
<div class="site-tagline">Latest news and analysis from Sweden's Riksdag. The Economist-style political journalism covering parliament, government, and agencies with systematic transparency.</div>
<div class="site-tagline"${lang !== 'en' ? ' lang="en"' : ''}>Latest news and analysis from Sweden's Riksdag. The Economist-style political journalism covering parliament, government, and agencies with systematic transparency.</div>

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 849b756. Added conditional lang="en" attribute for accessibility:

<div class="site-tagline"${lang !== 'en' ? ' lang="en"' : ''}>

const availableText = AVAILABLE_IN_TRANSLATIONS[currentLang] || 'Available in';
const badges = languages.map(lang => generateLanguageBadge(lang, isRTL)).join(' ');

return `<p class="available-languages"${isRTL ? ' dir="ltr"' : ''}><strong>${availableText}:</strong> ${badges}</p>`;
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generateAvailableLanguages() forces dir="ltr" on the entire <p> when the current UI language is RTL. That will make the translated “Available in” label render left-to-right (incorrect for Arabic/Hebrew). Prefer keeping the paragraph direction RTL and only force LTR on the individual badges/codes.

Suggested change
return `<p class="available-languages"${isRTL ? ' dir="ltr"' : ''}><strong>${availableText}:</strong> ${badges}</p>`;
return `<p class="available-languages"><strong>${availableText}:</strong> ${badges}</p>`;

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 849b756. Removed paragraph-level dir="ltr" to preserve RTL direction for Arabic/Hebrew labels. Individual badges still have dir="ltr" via generateLanguageBadge().

Comment on lines +3 to +20
/**
* Workflow State Coordinator
*
* Manages coordination between multiple news generation workflows to prevent
* duplicate work and improve efficiency:
* - news-realtime-monitor.md (2x daily)
* - news-evening-analysis.md (daily)
* - news-article-generator.md (various schedules)
*
* Features:
* - MCP query caching (2-hour TTL)
* - Similarity-based article deduplication (>70% threshold)
* - Recent article tracking (last 6 hours)
* - Workflow coordination metadata
*
* Usage:
* import { WorkflowStateCoordinator } from './workflow-state-coordinator.js';
* const coordinator = new WorkflowStateCoordinator();
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR title/description focus on moving article CSS and adding/repairing language badge styles, but this change set also introduces new workflow coordination and article quality-scoring modules plus new test suites and regenerates multiple news indexes. Please align the PR title/description with the actual scope, or split the unrelated features into separate PRs to reduce review/rollback risk.

Copilot uses AI. Check for mistakes.
Comment on lines 64 to 68
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}

await coordinator.save();
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn’t actually verify directory creation when missing: it creates the directory if it doesn’t exist, so coordinator.save() never has to create it. To test the behavior, ensure the directory is removed before calling save(), then assert save() recreates it.

Suggested change
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
await coordinator.save();
// Ensure any existing test file is removed
if (fs.existsSync(TEST_STATE_FILE)) {
fs.unlinkSync(TEST_STATE_FILE);
}
// Remove the directory to simulate a missing metadata directory
if (fs.existsSync(dir)) {
fs.rmdirSync(dir, { recursive: true });
}
expect(fs.existsSync(dir)).toBe(false);
await coordinator.save();
// save() should recreate the directory and state file
expect(fs.existsSync(dir)).toBe(true);

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 849b756. Test now removes directory first with fs.rmSync(dir, { recursive: true }), then verifies save() recreates it.

Comment on lines 22 to 27
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path, fileURLToPath, __filename, and __dirname are declared but not used in this module. Removing unused imports/variables will avoid lint noise and keeps the script focused.

Suggested change
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 849b756. Removed unused imports (path, fileURLToPath, __filename, __dirname), kept only fs.

Comment on lines 717 to 732
grid.innerHTML = articlesToRender.map(article => {
// Generate language badge for the article
const flag = {en:'🇬🇧',sv:'🇸🇪',da:'🇩🇰',no:'🇳🇴',fi:'🇫🇮',de:'🇩🇪',fr:'🇫🇷',es:'🇪🇸',nl:'🇳🇱',ar:'🇸🇦',he:'🇮🇱',ja:'🇯🇵',ko:'🇰🇷',zh:'🇨🇳'}[article.lang] || '🌐';
const langBadge = \`<span class="language-badge" aria-label="\${article.lang} language"><span aria-hidden="true">\${flag}</span> \${article.lang.toUpperCase()}</span>\`;

// Generate available languages display if multiple languages exist
const availableLangs = article.availableLanguages || [article.lang];
let availableDisplay = '';
if (availableLangs.length > 1) {
const availableText = '${AVAILABLE_IN_TRANSLATIONS[langKey] || 'Available in'}';
const availableBadges = availableLangs.map(l => {
const f = {en:'🇬🇧',sv:'🇸🇪',da:'🇩🇰',no:'🇳🇴',fi:'🇫🇮',de:'🇩🇪',fr:'🇫🇷',es:'🇪🇸',nl:'🇳🇱',ar:'🇸🇦',he:'🇮🇱',ja:'🇯🇵',ko:'🇰🇷',zh:'🇨🇳'}[l] || '🌐';
return \`<span class="lang-badge-sm"><span aria-hidden="true">\${f}</span> \${l.toUpperCase()}</span>\`;
}).join(' ');
availableDisplay = \`<p class="available-languages"><strong>\${availableText}:</strong> \${availableBadges}</p>\`;
}
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated client-side rendering duplicates the language flag map and “available languages” HTML building logic instead of using the newly added LANGUAGE_FLAGS / generateLanguageBadge() / generateAvailableLanguages() helpers. This increases maintenance cost and risks the HTML/CSS drifting (e.g., lang-badge-sm vs language-badge, missing RTL handling, different aria-labels). Consider generating these badges via the shared helpers or emitting the needed data and reusing the same functions in one place.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 849b756. Eliminated duplication by emitting LANGUAGE_FLAGS and AVAILABLE_IN_TEXT to client-side:

const LANGUAGE_FLAGS = ${JSON.stringify(LANGUAGE_FLAGS)};
const AVAILABLE_IN_TEXT = '${escapeHtml(AVAILABLE_IN_TRANSLATIONS[langKey])}';

Client code now uses these shared constants instead of inline flag maps.

Comment on lines 941 to 949
// Get ALL articles with language metadata for cross-language discovery
const allArticlesWithLanguageInfo = getAllArticlesWithLanguageInfo(articlesByLang);

const html = generateIndexHTML(langKey, allArticlesWithLanguageInfo, articlesByLang);
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getAllArticlesWithLanguageInfo(articlesByLang) is recomputed inside the per-language loop, even though articlesByLang doesn’t change. Compute it once before the loop and reuse it for each generateIndexHTML() call to avoid repeated work on every run.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 849b756. Moved getAllArticlesWithLanguageInfo() outside the loop for 14× performance improvement:

const allArticlesWithLanguageInfo = getAllArticlesWithLanguageInfo(articlesByLang);
Object.keys(LANGUAGES).forEach(langKey => { ... });

@pethers
Copy link
Member

pethers commented Feb 14, 2026

@copilot apply changes based on the comments in this thread

…irection, unused imports, and performance

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

@github-actions
Copy link
Contributor

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

@pethers
Copy link
Member

pethers commented Feb 14, 2026

@copilot analyse all changes. why news/index.html contains 32 links to all languages. should only contain english links to news. Analyse all news/index_*.html and workflow and code or test changed.

@pethers
Copy link
Member

pethers commented Feb 14, 2026

Analysis: Multi-Language Index Behavior

Current State ✅

All 14 news/index_*.html files show ALL 32 articles (cross-language discovery)

  • news/index.html (English): 32 articles (10 EN, 10 SV, 1 FR, 1 DE, etc.)
  • news/index_fr.html (French): 32 articles (same 32 articles)
  • All other language indexes: 32 articles each

Why This Was Implemented

This behavior was explicitly requested in Issue #155:

Original Problem Statement:

"News index files (news/index_*.html) for non-generated languages are NOT updated with new articles. Example: If English/Swedish articles are generated, news/index_fr.html won't contain links to these articles. Impact: French readers cannot discover English/Swedish breaking news"

Desired State (from Issue #155):

"✅ All 14 news/index_*.html files show ALL articles regardless of language
✅ Language badges indicate article availability ("Available in: EN, SV")
✅ Readers can discover relevant content across languages"

Implementation Details

File: scripts/generate-news-indexes.js
Lines 947-954: All indexes receive same article set

const allArticlesWithLanguageInfo = getAllArticlesWithLanguageInfo(articlesByLang);

Object.keys(LANGUAGES).forEach(langKey => {
  const html = generateIndexHTML(langKey, allArticlesWithLanguageInfo, articlesByLang);
  // ↑ All indexes get allArticlesWithLanguageInfo (32 articles)
});

Result: Each article displays:

  • Language badge (🇬🇧 EN, 🇸🇪 SV, etc.)
  • "Available in: EN, SV" indicator for multi-language articles

Alternative Behavior (Language-Specific Filtering)

If you want language-specific indexes instead:

  • news/index.html → Only EN articles (10 articles)
  • news/index_fr.html → Only FR articles (1 article)

This would require reverting lines 947-954 to:

Object.keys(LANGUAGES).forEach(langKey => {
  const articles = articlesByLang[langKey] || [];
  const html = generateIndexHTML(langKey, articles, articlesByLang);
});

Question for Clarification

Should news indexes show:

  1. Cross-language discovery (current): All indexes show all 32 articles with language badges ✅ (Issue Improve news-realtime-monitor.md: Multi-language synchronization and quality enhancement #155 requirement)
  2. Language-specific (original behavior): Each index shows only its language articles?

The current implementation follows Issue #155's explicit requirements for cross-language article discovery to help readers find relevant content regardless of generation language.

@pethers Please clarify which behavior you prefer, and I'll adjust accordingly.

@pethers
Copy link
Member

pethers commented Feb 14, 2026

Analysis: Why All Index Files Show All 32 Articles

Current Behavior

All 14 news/index_*.html files display all 32 articles regardless of language. This was implemented per Issue #155 requirements:

Original Problem Statement (Issue #155):

"News index files (news/index_*.html) for non-generated languages are NOT updated with new articles... If English/Swedish articles are generated, news/index_fr.html won't contain links to these articles... French readers cannot discover English/Swedish breaking news"

Desired State (Issue #155):

"✅ All 14 news/index_*.html files show ALL articles regardless of language
✅ Language badges indicate article availability ('Available in: EN, SV')"

Implementation Details

File: scripts/generate-news-indexes.js

Line 409-449: getAllArticlesWithLanguageInfo() function

function getAllArticlesWithLanguageInfo(articlesByLang) {
  const allArticles = [];
  
  // Collect ALL articles from ALL 14 languages
  Object.keys(articlesByLang).forEach(lang => {
    allArticles.push(...articlesByLang[lang]);
  });
  
  // Add availableLanguages field to each article
  // Returns array of all 32 articles with language metadata
}

Line 935-950: Index generation loop

// Compute ONCE for all indexes
const allArticlesWithLanguageInfo = getAllArticlesWithLanguageInfo(articlesByLang);

// Pass SAME list to ALL 14 language indexes
Object.keys(LANGUAGES).forEach(langKey => {
  const html = generateIndexHTML(langKey, allArticlesWithLanguageInfo, articlesByLang);
  // Each index gets all 32 articles with language badges
});

Current Distribution

  • English articles: 10
  • Swedish articles: 10
  • Other 12 languages: 1 each (12 articles)
  • Total: 32 articles shown in every index file

If Requirement Changed

To show only language-specific articles (news/index.html = EN only, news/index_fr.html = FR only):

Revert to original behavior:

// Line 935-950
Object.keys(LANGUAGES).forEach(langKey => {
  const articlesForThisLang = articlesByLang[langKey] || [];
  const html = generateIndexHTML(langKey, articlesForThisLang, articlesByLang);
  // Each index shows only its language articles
});

Result:

  • news/index.html: 10 articles (EN only)
  • news/index_sv.html: 10 articles (SV only)
  • news/index_fr.html: 1 article (FR only)

Question

Should I revert the cross-language discovery feature and restore language-specific filtering?

Each language index now displays only articles in that specific language:
- news/index.html (EN): 10 English articles (was 32 all languages)
- news/index_sv.html (SV): 10 Swedish articles (was 32 all languages)
- news/index_fr.html (FR): 1 French article (was 32 all languages)

Changed line 954 in generate-news-indexes.js from passing allArticlesWithLanguageInfo
(all articles) to articlesByLang[langKey] (language-specific articles).

This fixes the issue identified by @pethers where indexes were showing articles
in all languages instead of just their target language.

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@pethers
Copy link
Member

pethers commented Feb 14, 2026

Fixed in commit cd9e468.

Analysis Results:

  • Root Cause: Line 954 was passing allArticlesWithLanguageInfo (all 32 articles) to every language index
  • Issue: All indexes showed the same 32 articles from all 14 languages
  • Fix: Changed to articlesByLang[langKey] for language-specific filtering

After Fix:

  • news/index.html (EN): 10 English articles ✅
  • news/index_sv.html (SV): 10 Swedish articles ✅
  • news/index_fr.html (FR): 1 French article ✅
  • All other language indexes: 1 article each ✅

Each index now correctly shows only articles in its target language. Verified via Schema.org numberOfItems field and regenerated all 14 index files.

Copilot AI changed the title Move news article CSS to external stylesheet and add editorial tagline Externalize news article CSS, add quality framework, and implement workflow coordination Feb 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants