Skip to content

feat: add visual scannability — confidence chips, in-article TOC, progressive disclosure, methodology footer#2807

Merged
pethers merged 7 commits into
mainfrom
copilot/add-visual-scannability
May 28, 2026
Merged

feat: add visual scannability — confidence chips, in-article TOC, progressive disclosure, methodology footer#2807
pethers merged 7 commits into
mainfrom
copilot/add-visual-scannability

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 28, 2026

Articles are 200-300KB walls of continuous HTML with no visual differentiation between confidence levels, no orientation aids, and specialist content (60%+ of length) presented at the same weight as the editorial lede.

Changes

CSS component library (styles/components/article-scannability.css)

  • .rm-confidence--high/medium/low — green/amber/red chips with WCAG AA contrast
  • .rm-admiralty — badge with cursor: help for NATO Admiralty codes
  • .rm-timeline--urgent/near/horizon — urgency-coded timeline markers
  • .rm-article-toc — sticky sidebar nav, collapses to disclosure on mobile
  • .rm-disclosure — styled `` for specialist sections
  • .rm-methodology-footer — structured footer layout
  • Dark mode via prefers-color-scheme, scoped under html:not([data-theme="light"]) so a forced light theme always wins; RTL via logical properties

Post-processing transforms (scripts/render-lib/article-scannability.ts)

Six pure functions applied to sanitized HTML output (no sanitize-schema changes needed):
CODE_BLOCK_0_0

  • Confidence chips — regex wraps standalone HIGH/MEDIUM/LOW in colored spans
  • Admiralty badges — wraps (A1)(F6) with NATO-standard tooltips
  • Timeline indicatorsT+7dNext 7 days
  • Progressive disclosure — wraps Document Analysis, Intelligence Notes, etc. in ``. Section boundaries are resolved against the immutable original HTML and the output is assembled in a single forward pass, so consecutive matching headings can never produce malformed/duplicated <details> nesting.
  • TOC generation — builds `` from H2 headings (only when ≥2 exist)
  • Methodology footer — explains confidence methodology, Admiralty scale, i18n (EN/SV)

Heading text reused in the TOC and disclosure summaries is entity-decoded first, then stripped of tags (the strip is repeated until the string stabilises so overlapping/nested brackets cannot reconstitute markup), then re-escaped via the shared escapeHtml helper to neutralise any residual markup. Performing the tag strip as the final sanitization step resolves the CodeQL "Incomplete multi-character sanitization" alert.

Integration (scripts/render-lib/article.ts)

Transforms wired after renderMarkdownToHtml() + rewriteMarkdownHrefsInHtml(), before splitBodyAtSecondH2(). Inline IntersectionObserver script for TOC active-section highlighting injected conditionally.

CI (​.github/workflows/test-homepage.yml)

Raised the Build for Homepage Tests job timeout-minutes 15 → 20. The scannability transforms add ~0.55ms/article (~3s total); the build was hitting the 15-min cap because the news corpus has grown (main was already ~11m25s), not because of this feature.

Tests (tests/article-scannability.test.ts)

26 unit tests covering all transform functions, edge cases (no false positives on partial matches, nested HTML contexts, empty inputs), and heading-text escaping regressions for the TOC and disclosure summaries — including an overlapping-tag case that confirms tag stripping never reconstitutes markup.

@github-actions github-actions Bot added the size-xs Extra small change (< 10 lines) label May 28, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🏷️ Automatic Labeling Summary

This PR has been automatically labeled based on the files changed and PR metadata.

Applied Labels: size-xs

Label Categories

  • 🗳️ Content: news, dashboard, visualization, intelligence
  • 💻 Technology: html-css, javascript, workflow, security
  • 📊 Data: cia-data, riksdag-data, data-pipeline, schema
  • 🌍 I18n: i18n, translation, rtl
  • 🔒 ISMS: isms, iso-27001, nist-csf, cis-controls
  • 🏗️ Infrastructure: ci-cd, deployment, performance, monitoring
  • 🔄 Quality: testing, accessibility, documentation, refactor
  • 🤖 AI: agent, skill, agentic-workflow

For more information, see .github/labeler.yml.

@github-actions
Copy link
Copy Markdown
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

Copilot AI changed the title [WIP] Add visual confidence chips and in-article navigation Add article scannability layer: confidence/admiralty chips, sticky TOC, progressive disclosure, methodology footer May 28, 2026
Copilot AI requested a review from pethers May 28, 2026 15:53
…y badges, timeline indicators, progressive disclosure, sticky TOC, methodology footer)

Implements visual scannability enhancements for articles:
- Confidence labels (HIGH/MEDIUM/LOW) rendered as colored chips
- Admiralty codes (A1-F6) rendered as badges with tooltips
- Timeline markers (T+7d, T+30d, T+90d) rendered as urgency indicators
- Deep-dive sections wrapped in progressive disclosure (<details>)
- In-article sticky TOC generated from H2 headings
- Methodology transparency footer on every article
- Dark mode support for all new components
- RTL support via logical CSS properties
- IntersectionObserver-based TOC highlighting

Closes #2804

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@pethers pethers changed the title Add article scannability layer: confidence/admiralty chips, sticky TOC, progressive disclosure, methodology footer feat: add visual scannability — confidence chips, in-article TOC, progressive disclosure, methodology footer May 28, 2026
@github-actions github-actions Bot added html-css HTML/CSS changes testing Test coverage refactor Code refactoring size-l Large change (250-1000 lines) labels May 28, 2026
@github-actions
Copy link
Copy Markdown
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

Comment thread scripts/render-lib/article-scannability.ts Fixed
Comment thread scripts/render-lib/article-scannability.ts Fixed
@pethers pethers marked this pull request as ready for review May 28, 2026 16:48
Copilot AI review requested due to automatic review settings May 28, 2026 16:48
Copy link
Copy Markdown
Contributor

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

Adds a post-processing “scannability” layer to rendered news articles to improve orientation and visual parsing of long HTML outputs, by injecting a TOC, confidence/admiralty/timeline indicators, progressive disclosure wrappers, and a methodology footer, plus corresponding CSS and unit tests.

Changes:

  • Introduces article-scannability.ts with HTML-string transforms (chips/badges, timeline markers, disclosure wrapping, TOC + footer rendering) and wires it into the article render pipeline.
  • Adds a new CSS component file for the scannability UI (chips, badges, TOC, disclosure, methodology footer) and imports it in the global stylesheet.
  • Adds Vitest coverage for all transforms and their combined application.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
scripts/render-lib/article-scannability.ts Implements the HTML post-processing transforms and i18n strings for TOC/footer.
scripts/render-lib/article.ts Integrates scannability transforms into article rendering and conditionally injects TOC active-section highlighting script.
styles/components/article-scannability.css Provides styling for confidence/admiralty/timeline UI, TOC, disclosure widgets, and methodology footer (including dark/RTL handling).
styles.css Imports the new scannability component stylesheet.
tests/article-scannability.test.ts Adds unit tests covering each transform and the combined transform output.

Comment on lines +256 to +262
details.rm-disclosure > summary {
background: var(--badge-bg, #333);
}

details.rm-disclosure > summary:hover {
background: var(--section-border, #444);
}
Comment on lines +208 to +212
@media (prefers-color-scheme: dark) {
.rm-confidence--high {
background-color: rgba(30, 126, 52, 0.2);
color: #6fcf97;
border-color: rgba(111, 207, 151, 0.4);
Comment on lines +58 to +64
* Detect confidence labels (HIGH, MEDIUM, LOW) in rendered text and wrap
* them in styled chip spans. Only matches whole-word occurrences that appear
* after contextual keywords (confidence, assessment, rating, etc.) or inside
* parentheses following a letter-digit Admiralty code pattern.
*
* Pattern: "HIGH (A2)" or "CONFIDENCE: HIGH" or "confidence HIGH"
*/
Comment on lines +248 to +252
sourcesLabel: 'Source Verification',
sourcesDesc: 'All claims cite official Swedish government publications (Riksdagen, Regeringen) accessible via public APIs.',
freshnessLabel: 'Data Freshness',
freshnessDesc: 'Analysis generated from data retrieved within 24 hours of publication timestamp.',
},
Comment on lines +259 to +263
sourcesLabel: 'Källverifiering',
sourcesDesc: 'Alla påståenden refererar till officiella svenska myndighetspublikationer (Riksdagen, Regeringen) tillgängliga via offentliga API:er.',
freshnessLabel: 'Dataaktualitet',
freshnessDesc: 'Analys genererad från data hämtad inom 24 timmar från publiceringstidsstämpel.',
},
Comment on lines +293 to +297
export function applyScannabilityTransforms(bodyHtml: string, lang: Language): {
readonly transformedBody: string;
readonly tocHtml: string;
readonly methodologyFooterHtml: string;
} {
@github-actions
Copy link
Copy Markdown
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

… softened footer claims, theme-aware dark mode

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
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

Comment thread scripts/render-lib/article-scannability.ts Fixed
@pethers
Copy link
Copy Markdown
Member

pethers commented May 28, 2026

@copilot analyse and improve code and test quality so all workflows works

failing checks
Code scanning results / CodeQL
Code scanning results / CodeQLFailing after 4s — 1 new alert including 1 high severity security vulnerability
Test Homepage / Build for Homepage Tests (pull_request)
Test Homepage / Build for Homepage Tests (pull_request)Cancelled after 15m
skipped checks
Test Homepage / Homepage & Accessibility Tests (pull_request)
Test Homepage / Homepage & Accessibility Tests (pull_request)Skipped 3 minutes ago

…orm, raise build timeout

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

Copilot AI commented May 28, 2026

@copilot analyse and improve code and test quality so all workflows works

failing checks
...

Addressed both failing checks:

CodeQL (high-severity, article-scannability.ts:34) — the "Incomplete multi-character sanitization" alert is fixed. headingPlainText now decodes HTML entities first, then strips tags via a stripHtmlTags helper that loops until the string is stable, so overlapping/nested brackets (e.g. <scr<a>ipt>) can't reconstitute markup after a single pass and decoding can't reintroduce un-stripped tags. Insertion still goes through escapeHtml. Added a regression test for the overlapping-tag case.

Build for Homepage Tests (cancelled at 15m) — this was a job timeout, not a hang. main was already ~11m25s on 2026-05-23 and newer daily-news 14-language re-renders pushed the full build past the 15-min cap (the scannability transforms themselves add ~3s total across all renders — measured 0.55ms/article). Raised the job timeout-minutes 15 → 20, consistent with the prior 10 → 15 bump.

Also hardened transformProgressiveDisclosure: it now resolves section boundaries against the immutable original HTML and assembles output in one forward pass, fixing malformed/duplicated <details> nesting that occurred when consecutive headings matched.

build:lib type-check, the 26 scannability tests, and the article pipeline/aside/html-utils suites all pass.

@github-actions github-actions Bot added workflow GitHub Actions workflows ci-cd CI/CD pipeline changes labels May 28, 2026
@github-actions
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor

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 6 out of 6 changed files in this pull request and generated 4 comments.

Comment on lines +110 to +114
export function transformConfidenceChips(html: string): string {
// Match standalone "HIGH", "MEDIUM", "LOW" (whole-word, case-insensitive),
// optionally trailed by parenthetical Admiralty notation like "(B2)".
return html.replace(
/\b(HIGH|MEDIUM|LOW)\b(?=\s*(?:\([A-F][1-6]\))?)/gi,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 99a581a6. All three transforms (transformConfidenceChips, transformAdmiraltyBadges, transformTimelineIndicators) now use a replaceInTextNodes() helper that splits the HTML on tags and only applies the regex to text segments, leaving attributes untouched. Regression tests added to verify attributes like id="rm-medium-threats" and href="#rm-low-priority" remain unchanged.

Comment on lines +552 to 557
// Apply visual scannability transforms (confidence chips, admiralty
// badges, timeline indicators, progressive disclosure).
const { transformedBody, tocHtml, methodologyFooterHtml } = applyScannabilityTransforms(bodyHtml, input.lang);

const { lead: leadHtml, rest: restHtml } = splitBodyAtSecondH2(transformedBody);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 99a581a6. applyScannabilityTransforms() now only applies inline transforms (confidence/admiralty/timeline). Progressive disclosure is applied separately in article.ts after splitBodyAtSecondH2(), so only the rest chunk gets <details> wrappers — avoiding the malformed-HTML scenario where splitting happens inside a disclosure wrapper.

Comment on lines +143 to +147
/* Mobile: collapse TOC into a disclosure widget */
@media (max-width: 767px) {
.rm-article-toc {
position: static;
max-height: none;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 99a581a6. Updated the CSS comment to accurately state "disable sticky positioning and height constraints" — the mobile media query doesn't render a <details> disclosure widget.

Comment on lines +16 to +23
describe('transformConfidenceChips', () => {
it('wraps HIGH confidence in a green chip', () => {
const html = '<p>Confidence: HIGH (A2)</p>';
const result = transformConfidenceChips(html);
expect(result).toContain('class="rm-confidence rm-confidence--high"');
expect(result).toContain('aria-label="Confidence: HIGH"');
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 99a581a6. Added regression tests for all three transforms asserting that HTML attributes containing high|medium|low, (A2), and T+7d substrings remain unchanged after transformation.

@github-actions
Copy link
Copy Markdown
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

… comment, attribute-safety tests

- Confidence/admiralty/timeline transforms now use replaceInTextNodes() to
  avoid corrupting heading IDs, hrefs, and other HTML attributes.
- Progressive disclosure is applied after splitBodyAtSecondH2() to prevent
  malformed HTML when a disclosure-matching heading is the split point.
- CSS comment corrected: mobile TOC disables sticky, not disclosure.
- Regression tests added asserting attributes remain unchanged.

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
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

@pethers pethers merged commit 682b140 into main May 28, 2026
20 checks passed
@pethers pethers deleted the copilot/add-visual-scannability branch May 28, 2026 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-cd CI/CD pipeline changes html-css HTML/CSS changes refactor Code refactoring size-l Large change (250-1000 lines) size-xs Extra small change (< 10 lines) testing Test coverage workflow GitHub Actions workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add visual scannability: confidence chips, in-article navigation, and progressive disclosure

4 participants