Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
145 changes: 145 additions & 0 deletions .github/workflows/visual-diff.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
name: Visual diff

concurrency:
group: visual-diff-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

on:
pull_request:
types:
- opened
synchronize
reopened
labeled
unlabeled
edited
Comment on lines +9 to +15

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Fix invalid YAML list syntax in pull_request.types (workflow currently won’t parse).

Line 11 onward uses bare scalars instead of list items, which breaks YAML parsing and prevents the workflow from loading.

Suggested fix
 on:
   pull_request:
     types:
       - opened
-      synchronize
-      reopened
-      labeled
-      unlabeled
-      edited
+      - synchronize
+      - reopened
+      - labeled
+      - unlabeled
+      - edited
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
types:
- opened
synchronize
reopened
labeled
unlabeled
edited
types:
- opened
- synchronize
- reopened
- labeled
- unlabeled
- edited
🧰 Tools
🪛 actionlint (1.7.12)

[error] 11-11: could not parse as YAML: could not find expected ':'

(syntax-check)

🪛 YAMLlint (1.37.1)

[error] 12-12: syntax error: could not find expected ':'

(syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/visual-diff.yml around lines 9 - 15, The
pull_request.types section in the workflow file has invalid YAML syntax where
lines after the first item (synchronize, reopened, labeled, unlabeled, edited)
are missing the list item indicator. Add the dash and space prefix (- ) to the
beginning of each bare scalar line in the pull_request.types list to properly
format them as YAML list items, ensuring each trigger type follows the same
format as the opened item.

Source: Linters/SAST tools


permissions:
contents: read
pull-requests: write

jobs:
check:
runs-on: ubuntu-latest
outputs:
requested: ${{ steps.check.outputs.requested }}
steps:
- name: Detect visual diff request
id: check
env:
PR_LABELS: ${{ toJson(github.event.pull_request.labels) }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
requested=false
if printf '%s' "$PR_LABELS" | grep -q '"name": "visual-change"'; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verifies current vs hardened regex against compact and spaced JSON forms.
compact='[{"name":"visual-change"}]'
spaced='[{ "name": "visual-change" }]'

echo "$compact" | grep -q '"name": "visual-change"' && echo "current matched compact" || echo "current FAILED compact"
echo "$spaced"  | grep -q '"name": "visual-change"' && echo "current matched spaced"  || echo "current FAILED spaced"

echo "$compact" | grep -Eq '"name"[[:space:]]*:[[:space:]]*"visual-change"' && echo "hardened matched compact" || echo "hardened FAILED compact"
echo "$spaced"  | grep -Eq '"name"[[:space:]]*:[[:space:]]*"visual-change"' && echo "hardened matched spaced"  || echo "hardened FAILED spaced"

Repository: Cap-go/capgo

Length of output: 245


🏁 Script executed:

#!/bin/bash
# Check if the file exists and inspect the relevant section
if [ -f .github/workflows/visual-diff.yml ]; then
  echo "File found. Showing lines around line 34:"
  cat -n .github/workflows/visual-diff.yml | sed -n '25,45p'
else
  echo "File not found at expected location"
  find . -name "visual-diff.yml" 2>/dev/null
fi

Repository: Cap-go/capgo

Length of output: 1068


Fix label detection to handle both compact and spaced JSON formats from toJson().

The current pattern on line 34 only matches spaced JSON ("name": "visual-change"). When GitHub Actions produces compact JSON ("name":"visual-change"), the check fails and the visual-diff job is skipped even though the label exists.

Suggested fix
-          if printf '%s' "$PR_LABELS" | grep -q '"name": "visual-change"'; then
+          if printf '%s' "$PR_LABELS" | grep -Eq '"name"[[:space:]]*:[[:space:]]*"visual-change"'; then
             requested=true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if printf '%s' "$PR_LABELS" | grep -q '"name": "visual-change"'; then
if printf '%s' "$PR_LABELS" | grep -Eq '"name"[[:space:]]*:[[:space:]]*"visual-change"'; then
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/visual-diff.yml at line 34, The grep pattern on line 34
uses a literal string match that only handles spaced JSON format from toJson().
To support both compact and spaced JSON formats, modify the pattern to make the
whitespace around the colon optional using a regex pattern that matches either
"name": "visual-change" or "name":"visual-change". Update the grep command to
use the -E flag for extended regex and adjust the pattern accordingly to handle
both spacing variations.

requested=true
elif printf '%s' "$PR_BODY" | grep -q '<!-- visual-diff:required -->'; then
requested=true
fi
echo "requested=${requested}" >> "$GITHUB_OUTPUT"

visual-diff:
needs: check
if: needs.check.outputs.requested == 'true'
runs-on: ubuntu-latest
timeout-minutes: 45
name: Generate visual diff report
steps:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.x

- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup bun
run: bash scripts/setup-bun.sh

- name: Install dependencies
run: bun install

- name: Install Playwright browser
run: bunx playwright install --with-deps chromium

- name: Install Supabase CLI
uses: supabase/setup-cli@v2
with:
version: 2.84.2

- name: Link Supabase templates
run: ln -sfn supabase/templates templates

- name: Run visual diff pipeline
run: |
bun scripts/visual-diff.ts run \
--base "${{ github.event.pull_request.base.sha }}" \
--head "${{ github.event.pull_request.head.sha }}"

- name: Upload visual diff report
if: always()
uses: actions/upload-artifact@v6
with:
name: visual-diff-report-${{ github.event.pull_request.head.sha }}
path: .context/visual-diff/report/
if-no-files-found: ignore
retention-days: 14

- name: Comment visual diff summary on PR
if: always()
uses: actions/github-script@v8
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
JOB_STATUS: ${{ job.status }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
ARTIFACT_NAME: visual-diff-report-${{ github.event.pull_request.head.sha }}
with:
script: |
const fs = require('node:fs')
const path = require('node:path')
const marker = '<!-- capgo-visual-diff -->'
const issue_number = context.payload.pull_request.number
const summaryPath = path.join(process.env.GITHUB_WORKSPACE, '.context/visual-diff/report/summary.md')
const ok = process.env.JOB_STATUS === 'success'
const summary = fs.existsSync(summaryPath)
? fs.readFileSync(summaryPath, 'utf8').trim()
: 'Visual diff did not produce a report. Check the workflow logs and artifacts.'

const body = [
marker,
`## Visual diff ${ok ? 'passed' : 'failed'}`,
'',
summary,
'',
`Commit: \`${process.env.HEAD_SHA}\``,
`[Download the HTML report from workflow artifacts](${process.env.RUN_URL}) (artifact: \`${process.env.ARTIFACT_NAME}\`).`,
'',
'Open `index.html` from the artifact for side-by-side before/after/diff screenshots.',
].join('\n')

const { owner, repo } = context.repo
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number,
per_page: 100,
})
const existing = comments.find(comment => comment.body?.includes(marker))
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
})
}
else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body,
})
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fastlane/metadata/review_information/phone_number.txt

test-results
playwright-report
.context/visual-diff
playwright/.cache
playwright/.auth
CHANGELOG.md
Expand Down
40 changes: 40 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,46 @@ Key points:
`playwright/e2e` and run them locally with `bun run test:front` before
shipping UI changes.

### Visual diff for UI changes

When a PR changes customer-facing UI (layout, spacing, colors, components, or
copy placement), reviewers need a before/after screenshot diff.

**Request the automated PR report** by either:

- adding the `visual-change` label to the PR, or
- including `<!-- visual-diff:required -->` anywhere in the PR description.

On each push, the `Visual diff` GitHub Action captures screenshots from the PR
base commit and head commit, generates a diff report, uploads it as a workflow
artifact, and updates a sticky PR comment (`<!-- capgo-visual-diff -->`).

**Local workflow before opening or updating the PR:**

1. Capture the current UI baseline: `bun run visual:capture:before`
2. Apply your UI edits and rebuild or refresh the local app as needed.
3. Capture the updated UI: `bun run visual:capture:after`
4. Generate the report: `bun run visual:diff`
5. Paste `.context/visual-diff/report/summary.md` into the PR description under
`## Visual changes`.
6. Add the `visual-change` label (or the HTML marker above) so CI refreshes the
report on every push.

**Full local pipeline against `main`:**

```bash
bun run visual:run -- --base origin/main
```

**Route configuration:** edit `playwright/visual-diff.config.ts` when a PR
introduces a new screen that should be part of the visual diff set.

**Outputs:**

- `.context/visual-diff/before/` and `after/` — raw PNG captures
- `.context/visual-diff/report/index.html` — side-by-side before/after/diff view
- `.context/visual-diff/report/summary.md` — markdown table for the PR body

## Mobile Development

### Capacitor Configuration
Expand Down
10 changes: 8 additions & 2 deletions bun.lock

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

6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
"files:prod": "wrangler dev -c cloudflare_workers/files/wrangler.jsonc --env=prod --env-file=internal/cloudflare/.env.prod",
"stripe:emulator": "bun scripts/serve-stripe-emulator.ts",
"init:playwright": "bunx playwright install",
"visual:capture:before": "bun scripts/visual-diff.ts capture --phase before",
"visual:capture:after": "bun scripts/visual-diff.ts capture --phase after",
"visual:diff": "bun scripts/visual-diff.ts diff",
"visual:run": "bun scripts/visual-diff.ts run",
"test:front": "bun scripts/run-playwright-tests.ts",
"test:local": "bun run test:all",
"test:coverage:all": "bun run cli:build && bun run supabase:with-env -- bunx vitest run --coverage.enabled=true tests/*",
Expand Down Expand Up @@ -355,6 +359,8 @@
"jose": "^6.2.3",
"knip": "^6.17.1",
"oxlint": "^1.70.0",
"pixelmatch": "^7.2.0",
"pngjs": "^7.0.0",
"sass": "1.101.0",
"simple-git-hooks": "^2.13.1",
"supabase": "^2.106.0",
Expand Down
24 changes: 24 additions & 0 deletions playwright/visual-diff.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export interface VisualDiffRoute {
slug: string
path: string
/** When true, logs in as test@capgo.app before visiting the route. */
auth?: boolean
}

/**
* Console pages captured for before/after visual diffs.
* Add routes here when a PR touches a new screen reviewers should compare.
*/
export const visualDiffRoutes: VisualDiffRoute[] = [
{ slug: 'login', path: '/login/', auth: false },
{ slug: 'dashboard', path: '/dashboard', auth: true },
{ slug: 'apps', path: '/apps', auth: true },
{ slug: 'app-overview', path: '/app/com.demo.app', auth: true },
{ slug: 'channels', path: '/app/com.demo.app/channels', auth: true },
{ slug: 'devices', path: '/app/com.demo.app/devices', auth: true },
]

export const visualDiffViewport = {
width: 1280,
height: 720,
} as const
Loading
Loading