Skip to content

feat(slides): automated homepage highlights refresh#312

Open
yasinmiran wants to merge 19 commits into
mainfrom
feat/slides-automation
Open

feat(slides): automated homepage highlights refresh#312
yasinmiran wants to merge 19 commits into
mainfrom
feat/slides-automation

Conversation

@yasinmiran

Copy link
Copy Markdown
Collaborator

What this does

Adds a twice-weekly GitHub Actions job that keeps the homepage highlights carousel (src/data/slides.json) a fresh, rolling set of ≤6 slides, sourced from recent news/events/funding content, with no human in the loop. It exists because nobody currently takes the initiative to update the slides.

How it works

A deterministic Node pipeline does all ranking, file mutation, and safety checks, and can ship a complete carousel on its own:

cron ─► collect ─► select ─► (caption) ─► apply ─► validate + pnpm build ─► PR + auto-merge
  • collect ranks candidate articles by recency + event lifecycle + editorial topic weight, and pre-filters to covers that pass the image quality gates (so an unusable cover never gets picked).
  • select keeps evergreen pins, rotates bot slides with hysteresis + a per-run swap cap, and no-ops when nothing changed.
  • caption is an optional OpenCode pass that writes captions/alt for new slides; it is sandboxed to JSON-in/JSON-out and falls back to summary-derived text on any failure, so a flaky free model can never break a run.
  • apply copies covers, rewrites the JSON, and prunes only stale bot-created images.
  • validate + pnpm build are the machine sanity gate before merge.

Ownership model

Every slide carries one tag: evergreen: true (pinned) or sourceArticle (bot-managed). The bot only ever deletes image files named <year>-<slug>.<ext>, so legacy/human images are structurally safe. A slide added later via the CMS (no tag) is retained and auto-tagged evergreen, never dropped. This PR also tags the 5 existing slides.

Failure is loud

On any abort (validation, build, or PR/merge failure) the workflow opens or updates a single slides-bot issue rather than failing silently.

Required repo settings (before enabling)

  • Settings → Actions → General → Workflow permissions → Read and write + Allow GitHub Actions to create and approve pull requests.
  • Merge token: the workflow merges with gh pr merge --admin right after its own pnpm build gate. On an unprotected main, github.token works. If main has branch protection (required reviews or a required check like pr-test), add secret SLIDES_BOT_TOKEN (a fine-grained PAT with contents + PR write that can bypass the ruleset) — otherwise the merge fails loudly (opens an issue), it never silently stalls.
  • Optional caption agent: set repo variable SLIDES_AGENT_MODEL and secret OPENROUTER_API_KEY (or another free model). Without them the pipeline uses summary-derived captions and is fully functional.

Testing

33 unit tests (node --test scripts/slides/*.test.mjs), all passing on Node 24. End-to-end dry-runs (SLIDES_AGENT=off pnpm slides:refresh) verified the pipeline produces a valid carousel, touches only src/data/slides.json + src/data/slides/, and passes pnpm build. Operator commands: pnpm slides:collect (dry), pnpm slides:refresh, pnpm slides:validate.

Schedule

Mon 07:00 UTC + Fri 15:00 UTC (≈08:00 / 16:00 Europe/Oslo, ±1h DST drift; GitHub cron is UTC-only), plus manual dispatch.

Copilot AI review requested due to automatic review settings July 15, 2026 13:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Introduces a Node-based automation pipeline (plus tests and a scheduled GitHub Actions workflow) to keep the homepage highlights carousel (src/data/slides.json + src/data/slides/) refreshed automatically using recent site content, with validation and a build gate before auto-merge.

Changes:

  • Added a full slides refresh pipeline (collect → rank → select → optional captioning → apply → validate) with unit tests.
  • Added a scheduled + manually dispatchable GitHub Actions workflow to run the pipeline, open a PR, and auto-merge after pnpm build.
  • Bootstrapped ownership metadata in src/data/slides.json and added gray-matter dependency for frontmatter parsing.

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/data/slides.json Adds ownership tags (evergreen / sourceArticle) to bootstrap automation.
scripts/slides/validate-slides.test.mjs Tests slide validation rules, including legacy vs bot image quality gates.
scripts/slides/validate-slides.mjs Implements slide + image validation and diff-scope checks.
scripts/slides/slides.AGENTS.md Defines strict JSON-in/JSON-out contract for optional caption agent.
scripts/slides/select.test.mjs Tests selection logic (hysteresis, swap cap, untracked retention).
scripts/slides/select.mjs Implements deterministic selection/rotation of slides.
scripts/slides/refresh.mjs Orchestrates the end-to-end refresh run and sets workflow outputs.
scripts/slides/README.md Documents ownership model, safety guarantees, and operator commands.
scripts/slides/rank.test.mjs Tests ranking behavior (recency, event decay, anti-repeat).
scripts/slides/rank.mjs Implements scoring + ranking with editorial topic weighting.
scripts/slides/opencode.json Sandboxes OpenCode tool permissions for the caption agent step.
scripts/slides/image-probe.test.mjs Tests header-only probing for PNG/JPEG.
scripts/slides/image-probe.mjs Adds dependency-free image header probing (dimensions/format/bytes).
scripts/slides/frontmatter.test.mjs Tests article discovery and frontmatter parsing.
scripts/slides/frontmatter.mjs Reads article frontmatter (title/summary/date/cover) via gray-matter.
scripts/slides/dates.test.mjs Tests parsing of the repo’s free-text English date format.
scripts/slides/dates.mjs Implements strict free-text date parsing to UTC-midnight.
scripts/slides/constants.mjs Centralizes thresholds, regexes, and topic weighting constants.
scripts/slides/collect-candidates.test.mjs Tests candidate collection + cover quality filtering.
scripts/slides/collect-candidates.mjs Collects current slides + ranks eligible content candidates.
scripts/slides/caption-agent.test.mjs Tests agent fallback and hallucination safeguards.
scripts/slides/caption-agent.mjs Adds optional agent captioning with strict validation + fallback.
scripts/slides/apply-slides.test.mjs Tests transient-field stripping and bot-file pruning rules.
scripts/slides/apply-slides.mjs Applies selected slides, copies covers, and prunes stale bot images.
pnpm-lock.yaml Locks the added gray-matter dependency and transitive deps.
package.json Adds slides:* operator scripts and gray-matter dependency.
.github/workflows/refresh-highlights.yml Adds scheduled workflow to run refresh + build gate + PR/merge + failure issue reporting.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/slides/select.mjs
Comment on lines +3 to +6
const botFilename = c => `${c.year ?? '0000'}-${c.slug}.${c.coverExt}`;
const pick = s => ({src: s.src, alt: s.alt ?? null, caption: s.caption ?? null});
const sameSeq = (a, b) =>
JSON.stringify(a.map(pick)) === JSON.stringify(b.map(pick));
if (seen.has(s.src)) v.push(`${at} duplicate src: ${s.src}`);
seen.add(s.src);

if (!(s.evergreen === true) && !s.sourceArticle) v.push(`${at} untracked (no evergreen/sourceArticle)`);
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