Add monthly newsletter page#4082
Merged
Merged
Conversation
✅ Deploy Preview for nf-core-main-site ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Member
Author
|
@nf-core-bot fix linting |
mashehu
reviewed
Mar 23, 2026
mashehu
reviewed
Mar 23, 2026
mashehu
reviewed
Mar 24, 2026
8855524 to
a34c3d9
Compare
Create a newsletter page at /newsletter/[year]/[month] that aggregates monthly community content for email distribution: - Events this month + upcoming events (next 2 months) - Blog posts this month + "in case you missed it" (past 2 months) - Pipeline releases with highlighted first releases - New pipeline repositories and approved proposals from nf-core/proposals Uses email-friendly HTML (table-based layout, inline styles, 600px max-width) inside a #newsletter-content div for easy extraction. Month/year selector navigation sits outside this div. https://claude.ai/code/session_01DHK3ARtaSYevNH85QYBDBD
- Reorder events: upcoming events (next 2 months) is now the primary section, events this month shown smaller as secondary - Add nf-core/newsletter logo PNG generated from old-site base templates (light + dark background versions) - Force white background on #newsletter-content div so it's always readable regardless of web dark mode or email client dark mode - Logo displayed at top of newsletter using the light background variant https://claude.ai/code/session_01DHK3ARtaSYevNH85QYBDBD
- Add @media (prefers-color-scheme: dark) overrides for Apple Mail, Outlook Mac, and web browsers - Add [data-ogsc] selectors for Outlook iOS/Android apps - Swap logo images: light bg logo by default, dark bg logo in dark mode - Add nl-muted CSS class to secondary text elements for dark mode targeting with !important overrides over inline styles - Light mode inline styles serve as fallback for Gmail and other clients that strip <style> blocks https://claude.ai/code/session_01DHK3ARtaSYevNH85QYBDBD
Fetch logos from https://oldsite.nf-co.re/logo?t=newsletter instead of generating locally with incorrect font sizes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…heme The nf-core site uses Bootstrap's data-bs-theme attribute on <html> toggled via ThemeSwitch.svelte, not @media (prefers-color-scheme). The newsletter CSS was using the wrong selector, causing it to respond to OS dark mode setting instead of the site's theme toggle. Now uses :global([data-bs-theme="dark"]) for the web page, while keeping @media (prefers-color-scheme: dark) and [data-ogsc] for email client dark mode support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The data-bs-theme dark mode rules need !important to beat inline styles, and child class selectors (.nl-logo-light, .nl-logo-dark, .nl-muted) need :global() wrappers to prevent Astro from scoping them with hash attributes. Tested locally with dev server in both light and dark modes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multiple releases of the same pipeline in a month (e.g. pixelator v3.0.0 and v3.0.1) are now shown as a single row with multiple version badges, using the most recent release date. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Split proposals into "Pipeline Proposals" (strip "New pipeline:" prefix) and "Other Proposals" (RFCs, SIGs, etc. keep their prefix) - Show status badges: "Accepted" (green) for closed issues, "New" (blue) for issues opened that month - Include both newly opened and closed/accepted issues for the month - Sort accepted proposals first within each group Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…erImage Layout changes: - Widen email from 600px to 800px - Primary content (upcoming events, blog posts, first releases) is full-width - Secondary content (this month's events, older blogs, pipeline releases, proposals) uses 2-column table layout for visual hierarchy - Blog posts show header images with clickable links Blog images: - Make headerImage and headerImageAlt required in blog content schema - Add headerImage to the one blog post missing it (2020/data_management.md) - Add blogImageUrl() helper for unsplash size params Other fixes: - Sort release tags ascending (left to right) so latest tag + date are adjacent Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reorder sections to: blog posts, first releases, pipeline releases, upcoming events, events this month, older blog posts, pipelines & proposals. Add missing headerImage + headerImageAlt to: - 2025/paper-v2.mdx - 2026/configs-strict-syntax.mdx - 2026/statement-on-ai.mdx These were failing the build after headerImage was made required. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add optional headerImage/headerImageAlt fields to events schema - Events with headerImage show it in the newsletter's upcoming events - Events without headerImage fall back to the dynamic OG social card - Add headerImage to hackathon-boston event using existing summit card Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove OG social card fallback for events without a headerImage. Events without an explicit headerImage just show text, no image. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Local /assets/ paths don't exist as-is on the deployed site — they get processed through Astro's build pipeline into /_astro/ URLs. Use import.meta.glob to resolve the built URLs, matching how the blog page handles headerImage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The newsletter is at src/pages/newsletter/[year]/[month].astro (4 levels deep from src/), not 2 like the blog page. Fix the relative glob path and key prefix accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Glob-resolved assets return relative paths (/_astro/...) that work on any domain. Remove the nf-co.re fallback which broke deploy previews and hit the /assets/* → oldsite redirect. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The glob returns lazy loaders that need await, which can't be done in the template. Pre-resolve all blog and event image URLs into Maps during frontmatter execution, then look them up synchronously in the template. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tended In light mode, most links are dark (#333/#555) with only a few accent links in green. The dark mode was turning ALL links green via the blanket `a { color: #4ade80 }` rule. Now dark mode links default to light text (#e0e0e0) matching the body, with green reserved for elements that use the `nl-green` class: first-release pipeline names, proposals link, and footer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Make "Events That Happened This Month" and "In Case You Missed It" headings green (h2 style) so they stand out from list content - Upcoming events: 2-column layout with title left, date + tag right-aligned; full-width header image above when present - Switch to Astro <Image> component for blog and event images to get proper asset pipeline URLs (.netlify/images) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The relative ../../../../assets/** glob wasn't resolving correctly. Use Vite's root-relative /src/assets/** pattern instead, which reliably resolves regardless of file depth. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GitHub Issues have a state_reason field: "completed" or "not_planned". Only proposals closed as "completed" should appear as "Accepted" in the newsletter. Rejected proposals (closed as "not_planned") are now excluded entirely. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
a34c3d9 to
1f7cb09
Compare
- Resolve blog/event header images with Promise.all instead of two sequential await loops per generated month - Type allProposals as RawProposal[] instead of any[] in newsletter.ts and [month].astro - Extract the duplicated proposal-status sort comparator into a single sortByStatus helper Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UYthwAzUGhSqZc1FQGFf4p
…lic/ - email.astro: use `<style is:inline>` so the responsive media query ships inline and unscoped. Email clients drop external stylesheets (where Astro bundled the component CSS), and Astro scoping stopped the inline rule from matching the NewsletterContent elements, so columns never stacked on mobile. - Add nf-core/newsletter logos to public/images/logo/ for a stable asset URL usable from transactional emails. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jf58W9P1cwUmj2XekE6iMU
…e, homepage CTA
- NewsletterSignup.astro: reusable email sign-up form (Bootstrap input-group);
POSTs { email } to the newsletter service (Amazon SES double opt-in) with
inline success/error states. Re-init on astro:after-swap; supports multiple
instances per page.
- /newsletter/subscribe: dedicated page with the form plus "what you'll get",
"how it works" (double opt-in) and a privacy note.
- Homepage: a "Stay in the loop" sign-up section above the contributor carousel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jf58W9P1cwUmj2XekE6iMU
Review feedback (@mashehu): the alt text described "a journal article" but the header photo is lab glassware. Now describes the actual image (beakers, a conical flask and a pipette). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jf58W9P1cwUmj2XekE6iMU
Adds "Unsubscribe here" to the community footer in the email, carrying the SES
{{amazonSESUnsubscribeUrl}} placeholder that SES replaces at send time. Gated
behind a new `email` prop so the public web newsletter page doesn't show the
placeholder/link.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jf58W9P1cwUmj2XekE6iMU
Member
Author
|
@mashehu getting about finished now I think. I have to manually tweak some newsletter back-end settings each time I push a new commit here to avoid CORS errors, but can hopefully be tested right now. But would generally like to merge and refine as long as you're relatively happy? |
…il page - /newsletter: NewsletterSignup form in a card at the top of the listing. - /newsletter/<y>/<m>: a 'Subscribe to the newsletter' text link above the content. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jf58W9P1cwUmj2XekE6iMU
In email clients the dark-mode logo swap doesn't apply (the CSS is external and dropped), so the colored light-bg logo was unreadable on dark backgrounds. Give it an inline white background + rounded corners so it reads in both light and dark email modes. Web page is unaffected (its CSS-driven dark swap still works). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jf58W9P1cwUmj2XekE6iMU
- Bake a white rounded background into public/images/logo/nf-core-newsletter-lightbg.png so the masthead logo reads in email dark mode (CSS background on <img> is unreliable across email clients; baking it in is client-proof). - Reference the public/ logos directly (stable URLs, usable from email) and drop the duplicate src/assets/ copies + the bundled imports. - Revert the earlier inline background/border-radius on the <img>. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jf58W9P1cwUmj2XekE6iMU
- /newsletter/<y>/<m>/markdown: plain markdown (text/markdown) for agents — no images. - /newsletter/<y>/<m>/simple: unstyled HTML for pasting into the LinkedIn editor; images only for this month's new blog posts; shows a generated header image. - /newsletter/<y>/<m>/header.png: 1920x1080 header image (Satori -> sharp, like og.png) with the logo, date, tagline and monthly summary; logo fetched from the deployment origin and inlined (Satori doesn't fetch <img>; dimensions must be in the style). - Detail page: markdown + plain-HTML nav buttons alongside RSS/email, all with hover tooltips. - utils/newsletter-render.ts: shared URL/format helpers + markdown renderer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jf58W9P1cwUmj2XekE6iMU
mashehu
approved these changes
Jun 22, 2026
mashehu
left a comment
Contributor
There was a problem hiding this comment.
a bit bloated code, but that's what claude does...
marked some design discrepancies
Mechanical / housekeeping fixes from review of #4082: - Dedupe helpers: import eventTypeLabel/formatDate/formatAdvisoryTypes/ sortByStatus and the URL helpers from newsletter-render.ts instead of redefining them in NewsletterContent.astro - Rename severityColors -> severityBackGroundColors; give critical its own colour (#842029) so it's distinct from high - Badge contrast: dark text on the light event badges (talk/training); swap pure white (#ffffff) for #fefefe throughout - Use site colours in dark mode: #212529 background, #1a9655 green - Drop -apple-system/BlinkMacSystemFont in favour of system-ui - Logo alt text -> "nf-core newsletter logo"; blog images use headerImageAlt - content.config.ts: use new z.url() syntax - Nav: FontAwesome arrows + labelled format buttons (RSS/Email/Markdown/ Plain HTML), hide year on mobile; replace .newsletter-nav CSS with Bootstrap border-bottom/pb-3 - index: drop the lone arrow badge; subscribe: add filing-cabinet icon - Navbar: drop pointless justify-content-between on the Newsletter item - Symmetric spacing around proposal status badges; balance blog row layout Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- newsletter.ts: collapse the duplicated per-collection filter/sort and "adjacent months" logic into two generics (itemsInMonth / itemsInAdjacentMonths) driven by per-collection date accessors; replace the hand-rolled month-stepping loops with date-fns addMonths (noon-UTC anchored, timezone-safe). Public function signatures are unchanged. - Move the newsletter renderings out of components/ into layouts/: NewsletterContent.astro -> layouts/NewsletterLayout.astro, NewsletterSimple.astro -> layouts/NewsletterSimpleLayout.astro (NewsletterSignup stays a component). Update consumer imports. - Extract the prev/next month navigation into NewsletterNav.astro and use it for both the top and bottom navs (was duplicated), with a showFormats prop for the alternate-format buttons on the top nav only. - NewsletterLayout: tighten heading rhythm — more separation between sections, headings sit closer to their own content. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Simplification pass across the feature: - newsletter-render.ts: add a single EVENT_TYPES config (label + badge colours) as the one source of truth for event types; eventTypeLabel is now derived from it. Export a shared imgSrc() helper. - NewsletterLayout: drop the duplicated eventTypeColor/eventTypeTextColor maps (use EVENT_TYPES), the local imgSrc(), and the two inline advisory-URL regexes (use the shared advisoryUrl helper). Collapse the identical pipeline/other proposal blocks into one section map. Extract the dark-mode palette into SCSS variables shared by the three dark blocks (compiles to identical CSS). - NewsletterSimpleLayout: use the shared imgSrc() instead of a local copy. - newsletter.ts: add getNewsletterStaticPaths() returning the full Astro paths array; the four month routes now call it instead of repeating the months.map(...) boilerplate. No behavioural change — all routes (page/email/simple/markdown/rss) verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cover the build-time-critical pure functions in newsletter.ts that are easy to break silently and hard to catch in review: - getMonthName - getNewsletterMonths: dedupe across collections, newest-first sort, future-month filter - getBlogPostsForMonth: month filter + ordering - getBlogPostsForPreviousMonths: previous-month walk across the year boundary (date-fns) - getPipelineReleasesForMonth: tag grouping, first-release flag, numeric semver tag sort - getProposalsForMonth: pipeline/other categorisation, prefix stripping, new/accepted status Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- e2e: listing → month page renders (content, section headings, month nav, sign-up form) and the alternate formats (markdown/simple/rss) return the expected content + content-type. - Fix: the newsletter pages only set PageLayout's `title` (the visible hero), so the document <title> fell back to "nf-core" on every page. Add `meta_title` to the listing, month and subscribe pages — surfaced by the e2e title check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /markdown route sets Content-Type: text/markdown in `astro dev`, but it is prerendered to a static file that Netlify serves as text/plain — so the header check failed in CI (which tests the deployed preview). Assert on the body (`# nf-core/newsletter,`) instead, which holds in both dev and prod. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 2-column sections now use CSS grid (≥601px) so rows are equal-height and the
card dividers line up across both columns; inline-block stays the email fallback.
- "In case you missed it" merges recent events, earlier blog posts and earlier
advisories into one grid so both columns fill row-by-row (no orphaned gap), and
its heading now matches the other h2 sizes.
- "New pipelines & proposals" is one 2-column list instead of three subsections;
each row carries badge labels for type/status. Proposal type moved into the
subtitle ("Pipeline proposal #124"), and RFC-titled proposals read "RFC proposal".
- Factor out an advisoryBadgeStyle() helper (one colour lookup, shared style) and
a named RFC_TITLE_PATTERN.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Header images come in any aspect ratio and email clients ignore `object-fit`, so portrait/odd-ratio images rendered tall in the email. Crop the actual file instead, via getImage (cover, 480x270), for both bundled /assets images and whitelisted remote URLs: - Whitelist images.unsplash.com (the only domain used as a blog headerImage) so remote headers can be optimised/cropped at build time. - resolveImageSrc now returns a cropped JPEG thumbnail (JPEG, not WebP, for Outlook/email compatibility), falling back to the uncropped bundled asset / original URL if optimisation fails. - Event thumbnail 240x126 -> 240x135 to match the 16:9 crop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a "See details" link (to the subscribe page, which covers privacy etc.) after the description in the full sign-up box on /newsletter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The unit tests import the pure date/data helpers from newsletter.ts, but that
module had gained a top-level `import { getImage } from "astro:assets"` for the
image cropping. Playwright's plain Node ESM loader can't resolve the `astro:`
virtual scheme, so `playwright test` failed to even load the suite.
Move getNewsletterContentData (the only astro:assets consumer) into a new
utils/newsletter-content.ts, leaving newsletter.ts importable from plain Node.
Repoint the five route consumers. No behaviour change; getImage stays a static
import (in the new file). Export PipelineWorkflow for the moved signature.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…RLs) Three issues in the image cropping path, surfaced by local testing: - Resolve thumbnails sequentially, not via Promise.all: getImage lazily imports and caches the image service on first call, and the concurrent batch raced that init, so some images got an undefined service and fell back uncropped. - Use format "jpg" (the Netlify image service rejects "jpeg" as unsupported). - Re-add an email-only absolute-URL prefix: the cropped srcs are Netlify Image CDN URLs (/.netlify/images?…), which are root-relative and don't resolve in a mail client. Verified locally: web pages get relative cropped JPG thumbnails, the email build gets absolute ones, all newsletter routes 200, unit tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Event-type label/colour is presentation config that belongs in the rendering layer (EVENT_TYPES); "move to content collections" isn't the right home, so remove the aspirational TODO. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Create a newsletter page at /newsletter/[year]/[month] that aggregates
monthly community content for email distribution:
Uses email-friendly HTML (table-based layout, inline styles, 600px max-width)
inside a #newsletter-content div for easy extraction. Month/year selector
navigation sits outside this div.
https://claude.ai/code/session_01DHK3ARtaSYevNH85QYBDBD
@netlify /newsletter