Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4afe4a3
docs(slides): document ownership model and operator commands
yasinmiran Jul 15, 2026
74b9631
feat(slides): add constants and article date parser
yasinmiran Jul 15, 2026
18f8ad1
feat(slides): load article frontmatter across collections
yasinmiran Jul 15, 2026
b54556f
feat(slides): add dependency-free image dimension probe
yasinmiran Jul 15, 2026
8f5696c
feat(slides): rank candidates by recency, lifecycle and editorial weight
yasinmiran Jul 15, 2026
adeb64d
feat(slides): add collect CLI emitting current state and candidate pool
yasinmiran Jul 15, 2026
8b476a6
feat(slides): select slides with pinning, hysteresis and swap cap
yasinmiran Jul 15, 2026
8199430
test(slides): cover hysteresis and swap-cap backfill in selection
yasinmiran Jul 15, 2026
22678d1
feat(slides): add optional caption agent with summary fallback
yasinmiran Jul 15, 2026
cc48e95
feat(slides): apply selection by copying images and pruning stale files
yasinmiran Jul 15, 2026
5c1e507
feat(slides): add hard validation gate and diff-scope guard
yasinmiran Jul 15, 2026
95bc26c
fix(slides): apply image quality gates to bot-created images only
yasinmiran Jul 15, 2026
77617c5
test(slides): assert quality gates still fire on bot-named images
yasinmiran Jul 15, 2026
02f9844
feat(slides): add pipeline orchestrator
yasinmiran Jul 15, 2026
6faa6e0
fix(slides): skip candidates whose cover fails the bot quality gates
yasinmiran Jul 15, 2026
836a2c7
chore(slides): tag existing slides with ownership metadata
yasinmiran Jul 15, 2026
fad6999
ci(slides): add scheduled highlights refresh workflow
yasinmiran Jul 15, 2026
0883280
ci(slides): merge deterministically and report merge failures loudly
yasinmiran Jul 15, 2026
dbc89dc
fix(slides): retain untracked slides and require candidate summaries
yasinmiran Jul 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions .github/workflows/refresh-highlights.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Refresh Highlights

on:
schedule:
- cron: '0 7 * * 1' # Mon ~08:00 Europe/Oslo (UTC; ±1h DST drift)
- cron: '0 15 * * 5' # Fri ~16:00 Europe/Oslo
workflow_dispatch:

concurrency:
group: refresh-highlights
cancel-in-progress: false

permissions:
contents: write
pull-requests: write
issues: write

jobs:
refresh:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: pnpm

- name: Install dependencies
run: pnpm install

- name: Install OpenCode (pinned; agent is optional)
continue-on-error: true
run: npm install -g opencode-ai@0.5.29

- name: Run refresh pipeline
id: refresh
continue-on-error: true
env:
# Optional: set repo secrets to enable the caption agent. Absent → the
# pipeline uses summary-derived captions (fully functional).
SLIDES_AGENT_MODEL: ${{ vars.SLIDES_AGENT_MODEL }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
run: node scripts/slides/refresh.mjs --diff-scope

- name: Build sanity gate
id: build
if: steps.refresh.outcome == 'success' && steps.refresh.outputs.result == 'changed'
continue-on-error: true
run: pnpm build
env:
GITHUB_PAGES: true

- name: Open PR and merge
id: pr
if: steps.refresh.outputs.result == 'changed' && steps.build.outcome == 'success'
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.SLIDES_BOT_TOKEN || github.token }}
run: |
set -euo pipefail
BRANCH="bot/slides-refresh-${{ github.run_id }}"
git config user.name "elixir-no-bot"
git config user.email "actions@github.com"
git checkout -b "$BRANCH"
git add src/data/slides.json src/data/slides
git commit -m "chore(slides): refresh homepage highlights"
git push origin "$BRANCH"
PR_URL=$(gh pr create --base main --head "$BRANCH" \
--title "chore(slides): refresh homepage highlights" \
--body "Automated highlights refresh. Slide selection and captions were regenerated from recent content; validation and build passed.")
# The workflow already ran validation + pnpm build as its own gate, so
# merge immediately with --admin (deterministic, no waiting on pr-test,
# which does not run for GITHUB_TOKEN-created PRs). A failure here is
# loud: continue-on-error keeps the run going so the issue is opened.
gh pr merge "$PR_URL" --squash --admin --delete-branch

- name: Report failure
if: steps.refresh.outcome == 'failure' || steps.build.outcome == 'failure' || steps.pr.outcome == 'failure'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
TITLE="Highlights refresh failed"
BODY="The scheduled highlights refresh failed on run ${{ github.run_id }}. See the [workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})."
EXISTING=$(gh issue list --label slides-bot --state open --json number --jq '.[0].number // empty' || echo "")
if [ -n "$EXISTING" ]; then
gh issue comment "$EXISTING" --body "$BODY"
else
gh label create slides-bot --color BFD4F2 --description "Automated highlights refresh" 2>/dev/null || true
gh issue create --title "$TITLE" --label slides-bot --body "$BODY"
fi

- name: Fail job if pipeline, build, or merge failed
if: steps.refresh.outcome == 'failure' || steps.build.outcome == 'failure' || steps.pr.outcome == 'failure'
run: exit 1
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
"astro": "astro",
"postbuild": "pagefind --site dist",
"test:slugs": "node scripts/check-slugs.mjs",
"test:pages": "node scripts/test-pages.mjs"
"test:pages": "node scripts/test-pages.mjs",
"slides:collect": "node scripts/slides/collect-candidates.mjs",
"slides:refresh": "node scripts/slides/refresh.mjs",
"slides:validate": "node scripts/slides/validate-slides.mjs"
},
"dependencies": {
"@astrojs/check": "^0.9.0",
Expand Down Expand Up @@ -46,6 +49,7 @@
"@types/dompurify": "^3.2.0",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"gray-matter": "^4.0.3",
"npm-run-all2": "^9.0.2",
"pagefind": "^1.5.0"
},
Expand Down
77 changes: 77 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions scripts/slides/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Slides automation

Keeps `src/data/slides.json` (the homepage highlights carousel) fresh via a
twice-weekly GitHub Actions job. Canonical data lives in `src/data/slides.json`
and `src/data/slides/`; `public/data/slides/` is gitignored and regenerated at
build by `src/plugins/content-assets.mjs`. Never edit `public/`.

## Ownership tags

Every slide entry carries exactly one signal:

- `"evergreen": true`, pinned. Always kept, text frozen, image never deleted by
the bot. Set this to protect a slide.
- `"sourceArticle": "collection/year/slug"`, bot-managed. Scored from that
article each run; rotated by recency + editorial weight; dropped when it ages
out. `funding-and-projects` refs have two segments (no year).
- Neither key, treated as evergreen (fail closed) and logged. Should not occur
after bootstrap.

Bot-created image files are named `<year>-<slug>.<ext>`. The bot only ever
deletes files matching `^\d{4}-[a-z0-9-]+\.(png|jpe?g|webp)$` that are no longer
referenced and belonged to a `sourceArticle` entry, so legacy/human files
(none start with a 4-digit year) are structurally safe.

## CMS interaction

The `/admin` SlidesEditor seeds its form with `useState({ ...slide })` and edits
only `alt`/`caption`/`src`, so `sourceArticle`/`evergreen` survive both editing
and reordering. Ownership keys are preserved end to end; no action required.

## Operator commands

- `pnpm slides:collect`, print the ranked candidate pool + current state (dry).
- `pnpm slides:refresh`, run the full pipeline locally (writes files).
- `pnpm slides:validate`, run the sanity gate against the working tree.
- `bash scripts/manage-slides.sh`, interactive manual editor (unchanged).

The GitHub workflow `.github/workflows/refresh-highlights.yml` runs the pipeline
on cron (Mon 07:00 UTC, Fri 15:00 UTC) and on manual dispatch, then opens a PR
and auto-merges. On any hard failure it opens/updates one `slides-bot`-labelled
issue instead of merging.
Loading
Loading