Skip to content

Add monthly newsletter page#4082

Merged
ewels merged 82 commits into
mainfrom
claude/nf-core-newsletter-page-XyTgs
Jun 22, 2026
Merged

Add monthly newsletter page#4082
ewels merged 82 commits into
mainfrom
claude/nf-core-newsletter-page-XyTgs

Conversation

@ewels

@ewels ewels commented Mar 23, 2026

Copy link
Copy Markdown
Member

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

@netlify /newsletter

@netlify

netlify Bot commented Mar 23, 2026

Copy link
Copy Markdown

Deploy Preview for nf-core-main-site ready!

Name Link
🔨 Latest commit 9b59822
🔍 Latest deploy log https://app.netlify.com/projects/nf-core-main-site/deploys/6a39b1aae94b73000888ff9b
😎 Deploy Preview https://deploy-preview-4082--nf-core-main-site.netlify.app/newsletter
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot deleted a comment from netlify Bot Mar 23, 2026
@ewels ewels marked this pull request as draft March 23, 2026 11:45
@ewels

ewels commented Mar 23, 2026

Copy link
Copy Markdown
Member Author

@nf-core-bot fix linting

Comment thread sites/main-site/src/pages/newsletter/[year]/[month].astro Outdated
Comment thread sites/main-site/src/pages/newsletter/[year]/[month].astro Outdated
Comment thread sites/main-site/src/content/blog/2025/paper-v2.mdx Outdated
@mashehu mashehu force-pushed the claude/nf-core-newsletter-page-XyTgs branch from 8855524 to a34c3d9 Compare April 10, 2026 11:44
claude and others added 22 commits June 20, 2026 22:19
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>
@ewels ewels force-pushed the claude/nf-core-newsletter-page-XyTgs branch from a34c3d9 to 1f7cb09 Compare June 20, 2026 20:25
ewels and others added 3 commits June 20, 2026 23:10
- 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
@ewels ewels marked this pull request as ready for review June 21, 2026 09:11
ewels and others added 5 commits June 21, 2026 11:28
…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
@ewels

ewels commented Jun 21, 2026

Copy link
Copy Markdown
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?

ewels and others added 4 commits June 21, 2026 21:06
…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 mashehu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

a bit bloated code, but that's what claude does...
marked some design discrepancies

Comment thread sites/main-site/src/components/newsletter/NewsletterContent.astro Outdated
Comment thread sites/main-site/src/components/newsletter/NewsletterContent.astro Outdated
Comment thread sites/main-site/src/components/newsletter/NewsletterContent.astro Outdated
Comment thread sites/main-site/src/components/newsletter/NewsletterContent.astro Outdated
Comment thread sites/main-site/src/components/newsletter/NewsletterContent.astro Outdated
Comment thread sites/main-site/src/content.config.ts Outdated
Comment thread sites/main-site/src/components/newsletter/NewsletterContent.astro
Comment thread sites/main-site/utils/newsletter-render.ts
Comment thread sites/main-site/utils/newsletter.ts Outdated
Comment thread sites/main-site/utils/newsletter.ts
ewels and others added 12 commits June 22, 2026 18:24
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>
@ewels ewels merged commit 5132693 into main Jun 22, 2026
13 checks passed
@ewels ewels deleted the claude/nf-core-newsletter-page-XyTgs branch June 22, 2026 22:10
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.

4 participants