Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
a8e8f2c
Add per-PR visual diff via Playwright + Chromatic
RisingOrange Apr 20, 2026
7188517
Intercept external APIs via MSW-node for visual-diff runs
RisingOrange Apr 23, 2026
1e0b1a5
Include /posts in visual-diff coverage
RisingOrange Apr 24, 2026
85c27aa
Override mobile deviceScaleFactor to 1 to stay under Chromatic's cap
RisingOrange Apr 24, 2026
a387bab
Post a visual-diff scope comment on each PR
RisingOrange Apr 24, 2026
4f97009
Default-deny cross-origin iframes + XHR at browser boundary
RisingOrange Apr 24, 2026
bd3b964
Note limitation: same fork branch with multiple simultaneous PRs
RisingOrange Apr 24, 2026
7778906
Update visual-diff README for default-deny + scope comment
RisingOrange Apr 24, 2026
7dd119e
Note bypass-vs-catch-all policy split in msw-handlers
RisingOrange Apr 24, 2026
f74adce
Merge MSW setup and handlers into one msw-setup.ts
RisingOrange Apr 24, 2026
e3e04e7
Add file docstrings to routes.ts and smoke.spec.ts
RisingOrange Apr 24, 2026
823c5f8
Let shell env beat .env under VISUAL_TEST so fixtures are actually used
RisingOrange Apr 24, 2026
101ece6
Make national-groups fixture actually exercise /communities render path
RisingOrange Apr 24, 2026
31e8859
Update dependency knip to ^6.6.3 (#808)
renovate[bot] Apr 24, 2026
ebbb0b8
Update dependency knip to ^6.7.0 (#810)
renovate[bot] Apr 25, 2026
e831ba4
Add format command
Wituareard Apr 25, 2026
6ca61fc
Add support for forks to GitHub format command
Wituareard Apr 25, 2026
973f81b
Fix GitHub format command
Wituareard Apr 25, 2026
0da50b2
Update stefanzweifel/git-auto-commit-action action to v7 (#811)
renovate[bot] Apr 25, 2026
d0aa923
Aus progress update (#809)
PeterHorniak Apr 25, 2026
3365c8a
Potential fix for code scanning alert no. 10: Untrusted Checkout TOCT…
Wituareard Apr 25, 2026
7cc5adf
Update all non-major dependencies (#781)
renovate[bot] Apr 25, 2026
ecf24b1
Restructure /press to be a Markdown post (#776)
RisingOrange Apr 26, 2026
7b37de5
Migrate from husky and lint-staged to lefthook
Wituareard Apr 26, 2026
8a6c8a3
Aus pages general updates & q2 campaign prep (#813)
PeterHorniak Apr 26, 2026
376cd44
Add ESLint rule to disallow top-level script elements with src attrib…
Wituareard Apr 26, 2026
127fc33
Opt donate/pdoom/faq into visual-diff coverage
RisingOrange Apr 26, 2026
13c5c27
Merge remote-tracking branch 'origin/main' into visual-diff
RisingOrange Apr 26, 2026
40f9819
Resolve PR by head ref to avoid default-branch API quirk
RisingOrange Apr 26, 2026
9aacfd0
Fix "script in markdown" rule when preceded by other elements
Wituareard Apr 26, 2026
bdc2073
Update dependency typescript-eslint to ^8.59.1 (#815)
renovate[bot] Apr 27, 2026
6133410
Merge remote-tracking branch 'origin/main' into visual-diff
RisingOrange Apr 28, 2026
bd1b350
Updates to various Aus pages, testing of collapsible section on /aust…
PeterHorniak Apr 28, 2026
7edd555
Update dependency knip to ^6.8.0 (#817)
renovate[bot] Apr 29, 2026
4c0b505
Update dependency knip to ^6.9.0 (#818)
renovate[bot] Apr 29, 2026
a9de9d5
Update Post “ai-concerns” (#819)
jonathanpauseai Apr 30, 2026
a412510
Update Post “2024-february” (#820)
Maximophone Apr 30, 2026
49281e4
Create Post “uk-technology-secretary” (#821)
jonathanpauseai Apr 30, 2026
5651323
Update Post “uk-technology-secretary” (#822)
jonathanpauseai Apr 30, 2026
b4a9e4b
Add manual formatting workflow
Wituareard May 1, 2026
9bd0930
Format code with Prettier (#826)
github-actions[bot] May 1, 2026
15a6f7c
Update dependency svelte-check to ^4.4.7 (#825)
renovate[bot] May 1, 2026
bb2ec25
Update dependency eslint to ^10.3.0 (#828)
renovate[bot] May 1, 2026
b0a5823
Update roadmap with 2026 funding scenarios (#830)
Maximophone May 1, 2026
09e82e0
Update dependency knip to ^6.10.0 (#829)
renovate[bot] May 1, 2026
d904a63
Rewrite Theory of Change page (#831)
Maximophone May 1, 2026
5d5899c
Aus update of Tally form, assorted progress updates (#827)
PeterHorniak May 2, 2026
45d9173
Ignore all 404s for error reporting
Wituareard May 2, 2026
24c047d
Fetch Luma calendars sequentially to avoid rate limits
Wituareard May 2, 2026
510cbcc
Implement exponential backoff for Luma client and revert sequential f…
Wituareard May 2, 2026
69de1b2
Fix Luma client lint warnings and reduce max-warnings limit
Wituareard May 2, 2026
d808476
Update dependency knip to ^6.11.0 (#833)
renovate[bot] May 2, 2026
9cafdb0
Add fetchpriority=high to hero image (#834)
RisingOrange May 3, 2026
95bf439
Potential fix for code scanning alert no. 3: Inefficient regular expr…
Wituareard May 4, 2026
238fba9
Update Post “vacancies” (#837)
jonathanpauseai May 4, 2026
18c410e
Update dependency typescript-eslint to ^8.59.2 (#838)
renovate[bot] May 4, 2026
7c0588c
Update Post “uk-technology-secretary” (#839)
jonathanpauseai May 5, 2026
5dd996e
Remove redundant spaces
Wituareard May 5, 2026
ecae95d
Update dependency svelte-check to ^4.4.8 (#840)
renovate[bot] May 5, 2026
841a533
Update Post “uk-technology-secretary” (#842)
jonathanpauseai May 5, 2026
5e5e28d
Update dependency knip to ^6.12.0 (#844)
renovate[bot] May 6, 2026
7f42e8f
Update dependency knip to ^6.12.1 (#845)
renovate[bot] May 7, 2026
595e715
Update Post “theory-of-change” (#846)
Maximophone May 8, 2026
e878ab8
Update dependency knip to ^6.12.2 (#847)
renovate[bot] May 9, 2026
601a5f2
Update dependency typescript-eslint to ^8.59.3 (#849)
renovate[bot] May 11, 2026
783d3e6
Split hero into generic slogan + current-campaign section (#850)
Maximophone May 11, 2026
e140eda
Update Post “ai-not-just-coming-for-your-job” (#851)
Maximophone May 12, 2026
1e6752f
Update Post “ai-not-just-coming-for-your-job” (#852)
Maximophone May 12, 2026
57635d2
Update Post “ai-not-just-coming-for-your-job” (#853)
Maximophone May 12, 2026
5a9af06
Update Post “ai-not-just-coming-for-your-job” (#854)
jonathanpauseai May 12, 2026
1b59fda
Update Post “ai-not-just-coming-for-your-job” (#855)
Maximophone May 12, 2026
8508cff
Update Post “ai-not-just-coming-for-your-job” (#856)
jonathanpauseai May 12, 2026
1fba467
Update Post “ai-not-just-coming-for-your-job” (#857)
jonathanpauseai May 12, 2026
a79913b
Update Post “ai-not-just-coming-for-your-job” (#858)
Maximophone May 12, 2026
79d80b8
Update dependency knip to ^6.13.0 (#859)
renovate[bot] May 12, 2026
e0ecb8d
Update Post “ai-not-just-coming-for-your-job” (#860)
jonathanpauseai May 12, 2026
8074877
Update dependency knip to ^6.13.1 (#862)
renovate[bot] May 12, 2026
715c0e3
Fix homepage hero not filling viewport (+ cleanups) (#861)
RisingOrange May 12, 2026
e705814
UK MP email tool: AI Liability swap + embeddable route (#864)
UFO-101 May 14, 2026
9f79f60
Fix ESLint warnings
Wituareard May 14, 2026
3ba1c5d
Run lint commands in parallel
Wituareard May 14, 2026
8e820d9
Override "any" return type of Response#json
Wituareard May 14, 2026
1b9edb8
Add GTM tracking events for banners (#863)
Wituareard May 14, 2026
6992ee8
Rename AI Liability PDFs to remove spaces from filenames (#865)
UFO-101 May 14, 2026
4db38b4
Disable scheduled rebuild
Wituareard May 14, 2026
ae0681a
Update dependency svelte to v5.55.7 [SECURITY] (#867)
renovate[bot] May 15, 2026
1f5d167
Fix scheduled build being canceled for every action?
Wituareard May 15, 2026
39eeac2
Update dependency knip to ^6.14.0 (#868)
renovate[bot] May 15, 2026
6389c62
Merge remote-tracking branch 'origin/main' into visual-diff
RisingOrange May 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
82 changes: 82 additions & 0 deletions .github/workflows/format-command.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Format PR

on:
issue_comment:
types: [created]

jobs:
format:
name: Format Code
if: |
github.event.issue.pull_request &&
contains(github.event.comment.body, '/format') &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Acknowledge command
uses: actions/github-script@v9
with:
script: |
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: 'eyes'
});

- name: Get PR details
uses: actions/github-script@v9
id: get-pr
with:
script: |
const prNumber = context.issue.number;
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
core.setOutput('branch', pr.head.ref);
core.setOutput('sha', pr.head.sha);
core.setOutput('repo', pr.head.repo.full_name);

- name: Checkout PR branch
uses: actions/checkout@v6
with:
repository: ${{ steps.get-pr.outputs.repo }}
ref: ${{ steps.get-pr.outputs.sha }}
token: ${{ secrets.GITHUB_TOKEN }}

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

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run format
run: pnpm format .

- name: Commit and push changes
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: 'style: format code with prettier'
branch: ${{ steps.get-pr.outputs.branch }}

- name: React to comment
uses: actions/github-script@v9
with:
script: |
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: 'rocket'
});
43 changes: 43 additions & 0 deletions .github/workflows/manual-format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Manual Format

on:
workflow_dispatch:

jobs:
format:
name: Format Code
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v6

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

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

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run format
run: pnpm format .

- name: Create Pull Request
uses: peter-evans/create-pull-request@v8
with:
commit-message: 'Format code with Prettier'
title: 'Format code with Prettier'
body: |
This PR was automatically created by the **Manual Format** workflow.

It runs `pnpm format .` on the `main` branch to ensure code style consistency.
branch: manual-format
base: main
delete-branch: true
9 changes: 5 additions & 4 deletions .github/workflows/scheduled-build.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: Scheduled Rebuild

on:
schedule:
# Every 30 minutes
- cron: '*/30 * * * *'
workflow_dispatch:
# Allow manual triggering
# schedule:
# # Every 30 minutes
# - cron: '*/30 * * * *'
# workflow_dispatch:
# # Allow manual triggering

jobs:
rebuild:
Expand Down
88 changes: 88 additions & 0 deletions .github/workflows/visual-diff-comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Visual diff scope comment

# Posts (or updates) a sticky PR comment summarizing visual-diff coverage after
# each successful run of the Visual diff workflow. Runs via `workflow_run` so it
# executes in the base-repo context with write permissions, even for PRs from
# forks — GITHUB_TOKEN on the source `pull_request` workflow is read-only for
# forks.
#
# Safety: this workflow consumes an artifact produced by the fork's run, but
# only treats it as data — it renders the Markdown body into a PR comment and
# never executes any script from the fork's checkout. The PR number is derived
# from `workflow_run.head_sha` via the trusted GitHub API (not from anything
# the fork could tamper with), so a compromised artifact can't steer the
# comment onto an arbitrary PR.

on:
workflow_run:
workflows: [Visual diff (Chromatic)]
types: [completed]

permissions:
# Setting permissions at workflow level makes every unlisted scope `none` —
# actions: read is needed for the cross-run artifact download below.
actions: read
pull-requests: write

jobs:
comment:
runs-on: ubuntu-latest
if: >-
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: Resolve PR from trusted workflow_run payload
id: pr
uses: actions/github-script@v7
with:
script: |
const run = context.payload.workflow_run
// Look up open PRs by head ref directly. We deliberately don't use
// `listPullRequestsAssociatedWithCommit` here: when the commit is
// on the repo's default branch, that endpoint only returns merged
// PRs, so an open PR whose head is at this commit gets filtered
// out (hit on a fork where the feature branch was the default).
// `pulls?head=` has no such quirk.
const headOwner = run.head_repository?.owner?.login
const headBranch = run.head_branch
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${headOwner}:${headBranch}`
})
// Narrow by trusted workflow_run fields — head_repository.id and
// head_sha — so a commit shared between multiple open PRs routes
// the comment to the right one. Edge case we don't handle: same
// fork branch opened as TWO open PRs against different base
// branches simultaneously (rare). In that case `.find()` picks
// the first match and the comment may land on the wrong PR
// until one is closed.
const match = prs.find((p) => {
if (run.head_repository?.id && p.head.repo?.id !== run.head_repository.id) return false
if (p.head.sha !== run.head_sha) return false
return true
})
if (!match) {
core.info(`No open PR matches ${run.head_repository?.full_name}:${run.head_branch}@${run.head_sha}; skipping comment.`)
return ''
}
core.setOutput('number', String(match.number))
return String(match.number)
- name: Download scope artifact
id: download
if: steps.pr.outputs.number != ''
uses: actions/download-artifact@v4
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: visual-diff-scope
path: scope
- name: Post or update sticky comment
if: steps.pr.outputs.number != '' && steps.download.outcome == 'success'
uses: marocchino/sticky-pull-request-comment@v2
with:
header: visual-diff-scope
number: ${{ steps.pr.outputs.number }}
path: scope/body.md
118 changes: 118 additions & 0 deletions .github/workflows/visual-diff.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
name: Visual diff (Chromatic)

on:
push:
branches:
- main
pull_request:

permissions:
contents: read

concurrency:
group: visual-diff-${{ github.ref }}
cancel-in-progress: true

jobs:
chromatic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: pnpm/action-setup@v5
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Copy environment file
run: cp template.env .env
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Cache imagetools transforms
uses: actions/cache@v4
with:
path: node_modules/.cache/imagetools
key: imagetools-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'vite.config.ts', 'src/**/*.{png,jpg,jpeg,webp,avif,svg}', 'static/**/*.{png,jpg,jpeg,webp,avif,svg}') }}
restore-keys: |
imagetools-${{ runner.os }}-
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
playwright-${{ runner.os }}-
- name: Install Playwright (browser + deps on cache miss)
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium
- name: Install Playwright system deps (cache hit)
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium
- name: Run Playwright tests
run: pnpm exec playwright test
env:
VISUAL_TEST: '1'
# NPM_CONFIG_NODE_OPTIONS is the only way to propagate --import through
# pnpm: pnpm overwrites NODE_OPTIONS (with --experimental-global-webcrypto)
# when spawning scripts, but npm config values pass through untouched.
# See https://github.com/pnpm/pnpm/issues/6210
NPM_CONFIG_NODE_OPTIONS: '--import ./tests/visual/msw-setup.ts'
# Fake keys so the SDKs actually make HTTP requests (they short-circuit
# to a "no key" error otherwise, bypassing MSW entirely). The real
# requests are intercepted by MSW and served from fixtures.
AIRTABLE_API_KEY: 'fake-visual-test-key'
NOTION_API_KEY: 'fake-visual-test-key'
# Log of un-fixtured outbound requests (catch-all hits + unhandled
# bypasses). Surfaced by the scope-comment companion workflow.
MSW_WARN_LOG: ${{ github.workspace }}/msw-warn.log
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 14
- name: Publish to Chromatic
id: chromatic
# Inlined plaintext token — Chromatic's own recommended pattern for fork-PR
# support (forks can't read repo secrets). The token is a project-scoped
# write credential (anyone with it can run builds and consume snapshot
# quota for this project), rotatable from the Chromatic UI. See
# https://www.chromatic.com/docs/github-actions/ for the full rationale
# and any capability/scope details.
uses: chromaui/action@v16
with:
playwright: true
projectToken: chpt_cefd614c783fb5c
exitZeroOnChanges: true
exitOnceUploaded: true
- name: Generate scope comment
# Only on successful test runs — the comment describes what was
# covered, and an early-failed run has nothing meaningful to report.
# Runs after Chromatic publish so the scope comment can link to the
# Chromatic build URL (where reviewers accept/deny snapshots).
if: success() && github.event_name == 'pull_request'
env:
MSW_WARN_LOG: ${{ github.workspace }}/msw-warn.log
# The head SHA of the PR. The runner's GITHUB_SHA on pull_request
# events points at the auto-generated merge-ref commit, and a
# step-level env override of GITHUB_SHA doesn't beat the runner
# injection, so pass through a distinct variable.
SCOPE_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
CHROMATIC_BUILD_URL: ${{ steps.chromatic.outputs.buildUrl }}
run: |
mkdir -p scope
node --experimental-strip-types tests/visual/scope-comment.ts > scope/body.md
- name: Upload scope comment artifact
# Fork PRs can't be trusted to write an honest pr-number.txt, so the
# companion workflow derives the PR number from the workflow_run
# payload's head_sha (trusted, served by the base repo's API) instead.
if: success() && github.event_name == 'pull_request'
uses: actions/upload-artifact@v4
with:
name: visual-diff-scope
path: scope/
retention-days: 1
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,12 @@ CLAUDE.md
.env.sentry-build-plugin

.prettierignore

# Playwright
/test-results
/playwright-report

# Chromatic (only produced by local CLI runs, not CI)
/build-archive.log
/chromatic.log
/chromatic-diagnostics.json
1 change: 0 additions & 1 deletion .husky/pre-commit

This file was deleted.

3 changes: 0 additions & 3 deletions .lintstagedrc.json

This file was deleted.

5 changes: 5 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import globals from 'globals'
import ts from 'typescript-eslint'
import emptyMarkdownLinks from './eslint/plugin-empty-markdown-links.js'
import markdownScripts from './eslint/plugin-markdown-scripts.js'
import svelteConfig from './svelte.config.js'

// See https://typescript-eslint.io/troubleshooting/typed-linting/performance#changes-to-extrafileextensions-with-projectservice
Expand Down Expand Up @@ -40,7 +41,7 @@
if (!config.rules) return config

const warnedRules = Object.fromEntries(
Object.entries(config.rules).map(([key, value]) => [key, value.replace('error', 'warn')])

Check warning on line 44 in eslint.config.js

View workflow job for this annotation

GitHub Actions / pnpm-check

Unsafe member access .replace on an `any` value. (@typescript-eslint/no-unsafe-member-access)

Check warning on line 44 in eslint.config.js

View workflow job for this annotation

GitHub Actions / pnpm-check

Unsafe call of an `any` typed value. (@typescript-eslint/no-unsafe-call)
)

return { ...config, rules: warnedRules }
Expand All @@ -64,7 +65,11 @@
},
{
files: ['src/posts/**/*.md'],
plugins: {
markdownScripts
},
rules: {
'markdownScripts/no-script-with-src': 'error',
'no-restricted-syntax': [
'error',
{
Expand Down
Loading
Loading