feat(slides): automated homepage highlights refresh#312
Open
yasinmiran wants to merge 19 commits into
Open
Conversation
There was a problem hiding this comment.
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.jsonand addedgray-matterdependency 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 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)`); |
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.
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:
pnpm buildare the machine sanity gate before merge.Ownership model
Every slide carries one tag:
evergreen: true(pinned) orsourceArticle(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-taggedevergreen, 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-botissue rather than failing silently.Required repo settings (before enabling)
gh pr merge --adminright after its ownpnpm buildgate. On an unprotectedmain,github.tokenworks. Ifmainhas branch protection (required reviews or a required check likepr-test), add secretSLIDES_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.SLIDES_AGENT_MODELand secretOPENROUTER_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 onlysrc/data/slides.json+src/data/slides/, and passespnpm 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.