Conversation
…aselines New private workspace package `packages/github-app`: a Probot app that consumes the plugin's run manifest from workflow artifacts. - `workflow_run.completed`: resolves the pull request (also for forks), reads `.github/visual-regression.yml`, streams the matching artifacts to a disk cache (size-capped, zip-slip guarded), merges every manifest of the run and publishes a summary check run, one check run per failed screenshot (capped) and a single PR comment with old/diff/new thumbnails - `check_run.requested_action` and `issue_comment.created` (`/approve-visuals [names…]`): permission check, then one Git Data commit copying the run's `.actual.png` files over the baselines on the PR branch; compare-and-swap on the ref, blob-sha idempotency, stale-report detection, per-branch lock - images are served from the cache through HMAC-signed expiring links; `images: false` gives a text-only report - check runs are keyed by `external_id`, comments by a hidden marker, so redelivered webhooks update instead of duplicating Tests run the handlers end to end through `probot.receive` with nock. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- multi-stage `node:24-alpine` Dockerfile (repo root as context, pnpm filtered install, `pnpm deploy --prod`), compose files for local runs and for the server (external `nginx-proxy` network, `VIRTUAL_*` / `LETSENCRYPT_*` routing, named cache volume, restart: always), committed `.env.production` with secret placeholders - `deploy-github-app.yml`: manual workflow that builds and pushes the image to ghcr.io, then pulls and restarts it on the VPS over an SSH docker context (same shape as the frschool deploys) - `.github/visual-regression.yml` so the app can report on this repository's own pull requests (artifact `test`, project `examples/next`) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
This is the CI side of the story that #414 set up. The manifest tells you what happened in a run; this app reads it and puts the result where people actually look: on the pull request.
It's a Probot app living in
packages/github-app(private package, not published). The flow:if: always(), since the interesting runs are the failing ones).Visual regressioncheck run plus one PR comment with old / diff / new thumbnails for each screenshot that needs a look./approve-visuals(everything) or/approve-visuals `home page renders_#0`(some)..actual.pngbytes from the artifact over the baseline and pushes a single commit to the PR branch. No re-render, what you saw is what lands in git. It refuses when the branch moved on since the report, when you don't have write access, or when the PR comes from a fork (can't push there).A few things I want to call out:
images: falsein.github/visual-regression.ymlturns the report text-only. There's a "why a server" section in the package README.external_idand the comment by a hidden marker, so a redelivered webhook or a second click updates instead of duplicating. Approvals skip files whose baseline already has the same bytes.ci.workspace(added in feat: write a run manifest for CI consumers #414) to figure out the prefix, withprojectRootin the config as a fallback. That's why.github/visual-regression.ymlin this repo saysprojectRoot: examples/next.docker compose upon the VPS over an SSH docker context, joining thenginx-proxynetwork. Manualworkflow_dispatchonly.Tests run the three handlers end to end through
probot.receivewith nock, plus unit tests for the pure bits (manifest merging, path safety, tokens, report rendering, the Git Data commit with retry). Coverage is around 94% lines.What I still have to do by hand before this does anything:
APP_HOST(e.g.visual-regression.frsource.org) at the VPS so the cert can be issued.pnpm --filter @frsource/cpvrd-github-app devwithoutAPP_ID, open/probot) or manually with the permissions frompackages/github-app/app.ymland webhookhttps://<APP_HOST>/api/github/webhooks. Download the PEM.APP_ID,PRIVATE_KEY,WEBHOOK_SECRET,IMAGE_URL_SECRET(openssl rand -hex 32),FRSCHOOL_SSH_HOST,FRSCHOOL_SSH_PRIVATE_KEY, and the variableAPP_HOST.https://<APP_HOST>/healthz.examples/next.I couldn't run
docker compose buildlocally (no Docker daemon here), so the Dockerfile is untested until the first deploy.Based on
feat/manifest; GitHub should retarget it tofeat/major-5once #414 lands.Refs: #324
🤖 Generated with Claude Code