Conversation
…ation to TypeScript Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
There was a problem hiding this comment.
Pull request overview
This PR improves test quality signals by enabling comprehensive Vitest coverage collection and fixing Vitest 4 threshold configuration, and it ports the article navigation “fix” utility from Python to TypeScript while updating workflow documentation to reference the new TS fallback command.
Changes:
- Enable
coverage.all: trueand move coverage thresholds tocoverage.thresholdsinvitest.config.js(Vitest 4 format). - Add
scripts/fix-article-navigation.ts(TypeScript port of the existing Python fixer). - Update workflow documentation examples to use
npx tsx scripts/fix-article-navigation.tsinstead of the Python script.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.js | Enables full-file coverage reporting and fixes threshold config format for Vitest 4. |
| scripts/fix-article-navigation.ts | Adds a TS implementation of the navigation fixer (language switcher + article-top-nav). |
| .github/workflows/news-weekly-review.md | Updates fallback command reference to TS script. |
| .github/workflows/news-week-ahead.md | Updates fallback command reference to TS script. |
| .github/workflows/news-propositions.md | Updates fallback command reference to TS script. |
| .github/workflows/news-motions.md | Updates fallback command reference to TS script. |
| .github/workflows/news-monthly-review.md | Updates fallback command reference to TS script. |
| .github/workflows/news-month-ahead.md | Updates fallback command reference to TS script. |
| .github/workflows/news-evening-analysis.md | Updates script table entry to TS script. |
| .github/workflows/news-committee-reports.md | Updates fallback command reference to TS script. |
| .github/workflows/news-article-generator.md | Updates script table entry to TS script (including --dry-run). |
scripts/fix-article-navigation.ts
Outdated
|
|
||
| console.log('=== Summary ==='); | ||
| console.log(`Total files processed: ${total}`); | ||
| console.log(`Language switchers added/updated: ${switchersAdded}`); |
There was a problem hiding this comment.
The summary prints “Language switchers added/updated”, but the counter is incremented only when a switcher is newly added (not when it’s updated/replaced). Either adjust the counter to include updates or change the summary label so the reported numbers are accurate.
| console.log(`Language switchers added/updated: ${switchersAdded}`); | |
| console.log(`Language switchers added: ${switchersAdded}`); |
There was a problem hiding this comment.
Fixed in 41bce96 — label changed to Language switchers added: to match what the counter actually tracks.
| // Coverage thresholds — intentionally low during transition while all:true | ||
| // exposes previously-hidden zero-coverage files. Raise incrementally as | ||
| // tests are added for the dashboard and browser modules. | ||
| // Target thresholds (raise as coverage improves): lines:70, functions:70, branches:60, statements:70 | ||
| thresholds: { | ||
| lines: 25, | ||
| functions: 20, | ||
| branches: 25, | ||
| statements: 25, |
There was a problem hiding this comment.
This comment block updates the coverage thresholds, but the rationale references “all:true” transition; please keep the comment and the numeric thresholds in sync if the baseline changes, otherwise it’s easy for thresholds to drift from reality.
scripts/fix-article-navigation.ts
Outdated
| @@ -0,0 +1,241 @@ | |||
| #!/usr/bin/env node | |||
There was a problem hiding this comment.
The file is intended to be executed via npx tsx ..., but it has a #!/usr/bin/env node shebang. If someone runs the script directly (e.g., makes it executable and calls it), Node will try to execute TypeScript and fail. Align the shebang with the documented execution method (e.g., use a tsx-compatible shebang) or remove the shebang to avoid accidental misuse.
| #!/usr/bin/env node | |
| #!/usr/bin/env -S npx tsx |
There was a problem hiding this comment.
Fixed in 41bce96 — shebang updated to #!/usr/bin/env -S npx tsx.
Signed-off-by: James Pether Sörling <pethers@users.noreply.github.com>
|
@copilot apply changes based on the comments in this thread |
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
…tion.ts Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
scripts/fix-article-navigation.ts
Outdated
| * with a back-to-news link are present. Idempotent — safe to call multiple times. | ||
| * | ||
| * @param content Original HTML string | ||
| * @param baseSlug Article base slug (e.g. "news/2026-01-01-article") |
There was a problem hiding this comment.
The baseSlug parameter doc says it’s like "news/2026-01-01-article", but generateArticleLanguageSwitcher() expects a base slug without the news/ prefix (e.g. "2026-02-13-evening-analysis") and discoverArticles() currently produces that form. Updating this comment to reflect the real/required baseSlug format will help avoid introducing broken relative links (e.g. news/... resolving to news/news/...).
| * @param baseSlug Article base slug (e.g. "news/2026-01-01-article") | |
| * @param baseSlug Article base slug without "news/" prefix (e.g. "2026-01-01-article") |
There was a problem hiding this comment.
Fixed in 629e127 — JSDoc updated to "2026-01-01-article" (no news/ prefix).
tests/fix-article-navigation.test.ts
Outdated
| describe('transformContent — language switcher', () => { | ||
| it('inserts language-switcher immediately after <body>', () => { | ||
| const { content } = transformContent(BARE_ARTICLE, SLUG, 'en'); | ||
| // The switcher nav must appear right after <body> (possibly with a newline) | ||
| expect(content).toMatch(/<body>\s*<nav class="language-switcher"/); | ||
| }); | ||
|
|
||
| it('adds language-switcher when missing', () => { | ||
| const { content, addedSwitcher } = transformContent(BARE_ARTICLE, SLUG, 'en'); | ||
| expect(addedSwitcher).toBe(true); | ||
| expect(content).toContain('language-switcher'); | ||
| expect(content).toContain(`href="${SLUG}-en.html"`); | ||
| expect(content).toContain(`href="${SLUG}-sv.html"`); | ||
| }); | ||
|
|
||
| it('does not set addedSwitcher when switcher is already present', () => { | ||
| const { addedSwitcher } = transformContent(buildCompleteArticle(SLUG), SLUG, 'en'); | ||
| expect(addedSwitcher).toBe(false); | ||
| }); | ||
|
|
||
| it('generates all 14 language links', () => { | ||
| const { content } = transformContent(BARE_ARTICLE, SLUG, 'en'); | ||
| for (const lang of ['en', 'sv', 'da', 'no', 'fi', 'de', 'fr', 'es', 'nl', 'ar', 'he', 'ja', 'ko', 'zh']) { | ||
| expect(content).toContain(`hreflang="${lang}"`); | ||
| } | ||
| }); | ||
|
|
||
| it('marks current language as active with aria-current', () => { | ||
| const { content } = transformContent(BARE_ARTICLE, SLUG, 'sv'); | ||
| expect(content).toContain('aria-current="page"'); | ||
| expect(content).toContain(`href="${SLUG}-sv.html" class="lang-link active"`); | ||
| expect(content).not.toContain(`href="${SLUG}-en.html" class="lang-link active"`); |
There was a problem hiding this comment.
Tests use SLUG = 'news/...' and assert href="${SLUG}-xx.html", but the fixer discovers base slugs from filenames (e.g. 2026-02-13-evening-analysis) and the real article switchers use relative filenames without a news/ prefix. To keep tests representative of production usage and avoid masking path-related bugs, use a base slug without news/ and update the expectations accordingly.
| describe('transformContent — language switcher', () => { | |
| it('inserts language-switcher immediately after <body>', () => { | |
| const { content } = transformContent(BARE_ARTICLE, SLUG, 'en'); | |
| // The switcher nav must appear right after <body> (possibly with a newline) | |
| expect(content).toMatch(/<body>\s*<nav class="language-switcher"/); | |
| }); | |
| it('adds language-switcher when missing', () => { | |
| const { content, addedSwitcher } = transformContent(BARE_ARTICLE, SLUG, 'en'); | |
| expect(addedSwitcher).toBe(true); | |
| expect(content).toContain('language-switcher'); | |
| expect(content).toContain(`href="${SLUG}-en.html"`); | |
| expect(content).toContain(`href="${SLUG}-sv.html"`); | |
| }); | |
| it('does not set addedSwitcher when switcher is already present', () => { | |
| const { addedSwitcher } = transformContent(buildCompleteArticle(SLUG), SLUG, 'en'); | |
| expect(addedSwitcher).toBe(false); | |
| }); | |
| it('generates all 14 language links', () => { | |
| const { content } = transformContent(BARE_ARTICLE, SLUG, 'en'); | |
| for (const lang of ['en', 'sv', 'da', 'no', 'fi', 'de', 'fr', 'es', 'nl', 'ar', 'he', 'ja', 'ko', 'zh']) { | |
| expect(content).toContain(`hreflang="${lang}"`); | |
| } | |
| }); | |
| it('marks current language as active with aria-current', () => { | |
| const { content } = transformContent(BARE_ARTICLE, SLUG, 'sv'); | |
| expect(content).toContain('aria-current="page"'); | |
| expect(content).toContain(`href="${SLUG}-sv.html" class="lang-link active"`); | |
| expect(content).not.toContain(`href="${SLUG}-en.html" class="lang-link active"`); | |
| const BASE_SLUG = SLUG.replace(/^news\//, ''); | |
| describe('transformContent — language switcher', () => { | |
| it('inserts language-switcher immediately after <body>', () => { | |
| const { content } = transformContent(BARE_ARTICLE, BASE_SLUG, 'en'); | |
| // The switcher nav must appear right after <body> (possibly with a newline) | |
| expect(content).toMatch(/<body>\s*<nav class="language-switcher"/); | |
| }); | |
| it('adds language-switcher when missing', () => { | |
| const { content, addedSwitcher } = transformContent(BARE_ARTICLE, BASE_SLUG, 'en'); | |
| expect(addedSwitcher).toBe(true); | |
| expect(content).toContain('language-switcher'); | |
| expect(content).toContain(`href="${BASE_SLUG}-en.html`); | |
| expect(content).toContain(`href="${BASE_SLUG}-sv.html`); | |
| }); | |
| it('does not set addedSwitcher when switcher is already present', () => { | |
| const { addedSwitcher } = transformContent(buildCompleteArticle(BASE_SLUG), BASE_SLUG, 'en'); | |
| expect(addedSwitcher).toBe(false); | |
| }); | |
| it('generates all 14 language links', () => { | |
| const { content } = transformContent(BARE_ARTICLE, BASE_SLUG, 'en'); | |
| for (const lang of ['en', 'sv', 'da', 'no', 'fi', 'de', 'fr', 'es', 'nl', 'ar', 'he', 'ja', 'ko', 'zh']) { | |
| expect(content).toContain(`hreflang="${lang}"`); | |
| } | |
| }); | |
| it('marks current language as active with aria-current', () => { | |
| const { content } = transformContent(BARE_ARTICLE, BASE_SLUG, 'sv'); | |
| expect(content).toContain('aria-current="page"'); | |
| expect(content).toContain(`href="${BASE_SLUG}-sv.html" class="lang-link active"`); | |
| expect(content).not.toContain(`href="${BASE_SLUG}-en.html" class="lang-link active"`); |
There was a problem hiding this comment.
Fixed in 629e127 — SLUG changed to '2026-01-01-test' (bare slug, matching the form produced by discoverArticles()). All fixtures and buildCompleteArticle updated accordingly.
tests/fix-article-navigation.test.ts
Outdated
| expect(content).toContain('aria-current="page"'); | ||
| expect(content).toContain(`href="${SLUG}-sv.html" class="lang-link active"`); | ||
| expect(content).not.toContain(`href="${SLUG}-en.html" class="lang-link active"`); |
There was a problem hiding this comment.
A few assertions are sensitive to exact attribute ordering/spacing in the generated <a> tags (e.g. checking for the full substring href="..." class="lang-link active"). If generateArticleLanguageSwitcher() changes attribute order without changing semantics, these tests will fail. Consider using a regex/assertion that checks href, active class, and aria-current independently to make the tests resilient to harmless formatting changes.
| expect(content).toContain('aria-current="page"'); | |
| expect(content).toContain(`href="${SLUG}-sv.html" class="lang-link active"`); | |
| expect(content).not.toContain(`href="${SLUG}-en.html" class="lang-link active"`); | |
| // At least one link must indicate the current page | |
| expect(content).toContain('aria-current="page"'); | |
| // Find the <a> element for the current language (sv) | |
| const svLinkMatch = content.match(new RegExp(`<a[^>]*href="${SLUG}-sv.html"[^>]*>`)); | |
| expect(svLinkMatch).not.toBeNull(); | |
| const svLinkTag = svLinkMatch![0]; | |
| // It must have both lang-link and active classes (in any order) and aria-current="page" | |
| expect(svLinkTag).toMatch(/class="[^"]*\blang-link\b[^"]*\bactive\b[^"]*"/); | |
| expect(svLinkTag).toMatch(/aria-current="page"/); | |
| // Find the <a> element for a non-current language (en) | |
| const enLinkMatch = content.match(new RegExp(`<a[^>]*href="${SLUG}-en.html"[^>]*>`)); | |
| expect(enLinkMatch).not.toBeNull(); | |
| const enLinkTag = enLinkMatch![0]; | |
| // It must not be marked as active and must not have aria-current | |
| expect(enLinkTag).not.toMatch(/\bactive\b/); | |
| expect(enLinkTag).not.toMatch(/aria-current/); |
There was a problem hiding this comment.
Fixed in 629e127 — the marks current language as active test now extracts the <a> tag via regex and asserts lang-link, active, and aria-current independently, so harmless attribute-order changes won't break it.
|
@copilot apply changes based on the comments in this thread |
…ink assertions Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
scripts/fix-article-navigation.ts
Outdated
| const hasSwitcher = result.includes('language-switcher'); | ||
| if (!hasSwitcher) { | ||
| const switcherHtml = generateArticleLanguageSwitcher(baseSlug, lang); | ||
| result = result.replace(/(<body>)/, `$1\n${switcherHtml}`); |
There was a problem hiding this comment.
transformContent() inserts the language switcher immediately after <body>. In real news articles, the skip-link (<a ... class="skip-link">) is expected to be the first focusable element after <body> (see scripts/article-template/template.ts). Inserting the switcher before the skip-link will change keyboard navigation behavior and reduces accessibility. Consider inserting the switcher after the skip-link when it exists (and only falling back to after <body> when it doesn’t).
| result = result.replace(/(<body>)/, `$1\n${switcherHtml}`); | |
| // Prefer inserting AFTER the skip-link (<a class="skip-link">) when present, | |
| // so the skip-link remains the first focusable element after <body>. | |
| const bodyAndSkipLinkPattern = | |
| /(<body[^>]*>\s*(?:<!--[\s\S]*?-->[\s]*)?)(<a[^>]*class="skip-link"[^>]*>[\s\S]*?<\/a>)/; | |
| if (bodyAndSkipLinkPattern.test(result)) { | |
| result = result.replace( | |
| bodyAndSkipLinkPattern, | |
| `$1$2\n${switcherHtml}`, | |
| ); | |
| } else { | |
| // Fallback: insert immediately after <body> when no skip-link exists. | |
| result = result.replace(/(<body[^>]*>)/, `$1\n${switcherHtml}`); | |
| } |
There was a problem hiding this comment.
Fixed in 6afac64. When a skip-link is present, the switcher is now inserted after it (preserving it as the first focusable element). Falls back to inserting right after <body> when no skip-link exists. Added a test covering both cases.
|
@copilot apply changes based on the comments in this thread |
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
…v order Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
all: truein vitest.config.js coverage configurationthresholdssub-object for Vitest 4)scripts/fix-article-navigation.pytoscripts/fix-article-navigation.ts#!/usr/bin/env node→#!/usr/bin/env -S npx tsx.mdfiles to usenpx tsx scripts/fix-article-navigation.tsscripts/article-template/processArticle()to also repairarticle-top-navblocks missingback-to-newstransformContent()and addtests/fix-article-navigation.test.ts(16 tests covering switcher insertion, 14-language generation, active-language marking, skip-link ordering, Pattern A/B top-nav insertion, missing back-to-news repair, and idempotency)baseSlugJSDoc:"2026-01-01-article"(withoutnews/prefix)SLUG = '2026-01-01-test'(nonews/prefix) in tests to match productiondiscoverArticles()formatactive/aria-current<body>Original prompt
all: trueand identify untested code #680✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.