Skip to content

✨ Add thoughts canvas layout editor with pin, resize, and save #128

✨ Add thoughts canvas layout editor with pin, resize, and save

✨ Add thoughts canvas layout editor with pin, resize, and save #128

Workflow file for this run

name: PR Screenshots
on:
pull_request:
branches: [main]
permissions:
contents: write
pull-requests: write
concurrency:
# All PRs write to the same branch (`pr-screenshots`), so serialize updates to avoid
# non-fast-forward pushes overwriting each other.
group: pr-screenshots
cancel-in-progress: false
jobs:
screenshots:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Install pnpm
uses: pnpm/action-setup@v5
with:
cache: true
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium
- name: Generate screenshots
run: pnpm run gen:screenshots
- name: Publish screenshots to branch
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
set -euo pipefail
tmp_dir="$(mktemp -d)"
cp -r screenshots "$tmp_dir/screenshots"
PR_NUMBER="${{ github.event.pull_request.number }}"
BRANCH="pr-screenshots"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Start from the existing `pr-screenshots` branch (if it exists) so we
# accumulate screenshots from multiple PRs under `pr-<number>/...`.
if git fetch origin "$BRANCH" --depth=1; then
git checkout -B "$BRANCH" "origin/$BRANCH"
else
git fetch origin main --depth=1
git checkout -B "$BRANCH" origin/main
fi
# Replace only this PR's folder.
rm -rf "pr-${PR_NUMBER}"
mkdir -p "pr-${PR_NUMBER}"
mv "$tmp_dir/screenshots" "pr-${PR_NUMBER}/screenshots"
rm -rf "$tmp_dir"
git add "pr-${PR_NUMBER}/screenshots/*.png"
if git diff --cached --quiet; then
echo "No changes to commit."
exit 0
fi
git commit -m "πŸ“ Add PR #${PR_NUMBER} screenshots"
git push origin "$BRANCH":"$BRANCH"
- name: Comment on PR with screenshots link
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const marker = '<!-- yuler.dev/pr-screenshots -->';
const { owner, repo } = context.repo;
const issue_number = context.payload.pull_request.number;
const screenshotBranch = `pr-screenshots`;
// All comment URLs point at `pr-screenshots` branch content (tree + raw), never workflow artifacts.
const prFolderUrl = `https://github.com/${owner}/${repo}/tree/${screenshotBranch}/pr-${issue_number}`;
// Same-origin raw URLs for inline images (GitHub CSP).
const rawImgBase = `https://github.com/${owner}/${repo}/raw/${screenshotBranch}/pr-${issue_number}/screenshots`;
const files = fs
.readdirSync('screenshots')
.filter((f) => f.endsWith('.png'))
.sort((a, b) => a.localeCompare(b));
// screenshots/<slug>.<vp>.png
// Example: screenshots/home.iphone.png
const shotsByPage = new Map();
for (const file of files) {
const match = file.match(/^(.+)\.(iphone|ipad|pc)\.png$/);
if (!match) continue;
const [, slug, vp] = match;
if (!shotsByPage.has(slug)) shotsByPage.set(slug, {});
shotsByPage.get(slug)[vp] = file;
}
const pageSlugs = Array.from(shotsByPage.keys()).sort((a, b) =>
a.localeCompare(b),
);
const imgSrc = (file) => `${rawImgBase}/${file}`;
const pageCell = (slug) => {
return `<b>${slug}</b>`;
};
const cell = (slug, vp) => {
const file = shotsByPage.get(slug)?.[vp];
if (!file) return '-';
const src = imgSrc(file);
return `<img src="${src}" alt="${slug} ${vp}" width="260" />`;
};
const table = [
'| page | iphone | ipad | pc |',
'| --- | --- | --- | --- |',
...pageSlugs.map(
(slug) =>
`| ${pageCell(slug)} | ${cell(slug, 'iphone')} | ${cell(slug, 'ipad')} | ${cell(slug, 'pc')} |`,
),
].join('\n');
const body = [
marker,
`<details>`,
`<summary>πŸ“Έ Screenshots for PR #${issue_number}</summary>`,
``,
table,
``,
`[PR screenshots folder](${prFolderUrl}) (branch \`${screenshotBranch}\`)`,
`</details>`,
].join('\n');
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number,
per_page: 100,
});
const existing = comments.find((c) => c.body && c.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,
});
}