Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
229 changes: 229 additions & 0 deletions .claude/skills/pr-screenshot/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
---
name: pr-screenshot
description: "Capture and attach frontend screenshots and videos to pull requests for quantfive/django-react-intro. Auto-triggers when creating a PR with UI changes in the web Create React App. Uses Playwright to render affected pages and uploads evidence to the PR. IMPORTANT: Use this skill EVERY TIME you create a PR if the diff includes frontend/UI files. Trigger on: 'make a pr', 'create a pr', 'open a pull request', or any request that results in a PR with frontend file changes."
user_invocable: true
codepress_generated: true
---

# PR Screenshots — django-react-intro

Capture useful visual evidence for UI changes in the `web/` Create React App. The
app currently has one public route, `/`, and no authentication or API data
dependency. Use semantic assertions so the capture proves the rendered feature
is present instead of producing a screenshot of an empty shell.

## When to Trigger

Run this skill automatically when a pull request changes visible UI, including:

- `web/src/**/*.js` or `web/src/**/*.jsx`
- `web/src/**/*.css`
- `web/src/**/*.svg`
- `web/public/**/*` when the asset is rendered by the app
- Any file that changes the visible home page or its layout

Do not trigger for backend-only changes under `server/`, test-only changes with
no visual effect, documentation, or dependency updates that do not affect the
rendered page.

## What to Capture

| Changed files | What to screenshot | URL |
| --- | --- | --- |
| `web/src/containers/home/**` | Home page and its welcome content | `/` |
| `web/src/containers/App/**` | Routed app shell and home page | `/` |
| `web/src/containers/router/**` | Routed app shell and home page | `/` |
| `web/src/index.css`, `web/src/containers/home/stylesheets/**` | Home page styling | `/` |
| `web/src/**/*.svg`, `web/public/**` | The page that renders the changed asset | `/` |

Use a screenshot for static layout, color, typography, or asset changes. Use a
video when the change adds an animation, transition, hover state, or interaction.
Capture both when a static comparison and an interaction demonstration are useful.

## Auth and Data

The current app has no auth gate and the `/` route renders bundled content. No
cookies, local-storage tokens, login flow, or API mocks are required. If a future
change adds protected routes or network data, update this section and the capture
spec with the repo's real fixture or a narrowly-scoped `page.route()` mock.

## Dev Server

### Preferred CodePress container flow

When `.codepress/start-app-server/recipe.json` exists, invoke the repo's
`start-app-server` skill and use the returned container with
`take_app_server_screenshot`. The app server recipe is the source of truth for
the Dockerfile, port, and any future services.

```text
Skill({"skill": "start-app-server"})

take_app_server_screenshot(
containerId=<container id>,
path="/",
viewport={"width": 1440, "height": 1100},
wait_ms=1000,
)
```

The screenshot must show the home page's rendered content, including the main
heading or the changed feature. Stop the container after capture when it was
started only for this PR evidence.

### Local Playwright flow

Run from `web/`. The checked-in Yarn 1 lockfile is authoritative. If a global
Yarn binary is unavailable, use the pinned runner shown below.

```bash
cd web
npx --yes yarn@1.22.22 install --frozen-lockfile
mkdir -p /tmp/pr-screenshots
./node_modules/.bin/playwright install chromium
```

The checked-in `web/e2e/playwright.config.ts` starts CRA on `0.0.0.0:3000` with
`BROWSER=none` and waits for `http://127.0.0.1:3000` to respond. This legacy
CRA 1.0.10 tree requires Node 18 because its `websocket-driver` dependency
uses the removed Node `http_parser` binding; the config obtains Node 18 through
the pinned npm package and runs CRA's installed start script without fetching
application dependencies at server startup.

## Capture Spec Template

Create a temporary spec at `web/e2e/tests/_pr-screenshot.spec.ts`:

```typescript
import { expect, test } from '@playwright/test';

test('capture the changed home page', async ({ page }) => {
await page.goto('/');

const heading = page.getByRole('heading', { name: 'Welcome to React' });
await expect(heading).toBeVisible();
await heading.scrollIntoViewIfNeeded();
await page.waitForTimeout(500);

await page.screenshot({
path: '/tmp/pr-screenshots/pr-screenshot-home.png',
fullPage: false,
});
});
```

Replace the heading assertion with the most stable semantic locator for the
changed feature. Assert that feature before saving the screenshot, and choose a
viewport or scroll position that keeps the feature and nearby context visible.
Avoid relying on the browser's default 1280x720 viewport.

### Video Capture Template

For an interaction or animation, use Playwright video recording and keep the
useful portion under ten seconds when possible:

```typescript
import { expect, test } from '@playwright/test';

test.use({
video: { mode: 'on', size: { width: 1280, height: 720 } },
viewport: { width: 1280, height: 720 },
});

test('capture the changed interaction', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { name: 'Welcome to React' })).toBeVisible();

// Perform the interaction here, with short waits between meaningful actions.
await page.waitForTimeout(750);
await page.close();
});
```

Convert WebM to an inline GIF for GitHub and keep the original WebM as a link:

```bash
for video in /tmp/pr-screenshots/*.webm; do
[ -f "$video" ] || continue
ffmpeg -i "$video" -vf 'fps=15,scale=1280:-1:flags=lanczos' -y "${video%.webm}.gif"
done
```

## Running the Capture

```bash
cd web
mkdir -p /tmp/pr-screenshots
./node_modules/.bin/playwright test \
--config e2e/playwright.config.ts \
e2e/tests/_pr-screenshot.spec.ts \
--workers=1
```

Before reporting success, verify that each expected screenshot exists and is
larger than 10 KB. A blank page, loading shell, or screenshot containing only
the browser background is not useful evidence. If the app fails to start, read
the full Playwright error, repair the root setup issue, and retry up to three
times. Do not replace a broken render with a mocked screenshot.

## Before and After

For visual changes, capture the branch and the same page from the merge-base so
reviewers can compare the change in context:

1. Capture the current branch into a clean output directory.
2. Compute `BASE_SHA=$(git merge-base origin/master HEAD)`.
3. Add a detached worktree at that SHA, copy only the temporary spec/config and
required untracked environment files, install its dependencies, and run the
same capture command.
4. Save baseline files with a `-before` suffix beside the branch captures.
5. Remove the temporary worktree. If the base cannot render because this is a
new page or the fixture has changed, keep the branch capture and document the
after-only fallback.

Stamp the Demo section with `git rev-parse --short HEAD` so stale images are
detectable after later commits.

## Upload and Embed

In CodePress sessions, call `upload_pr_asset` for each PNG, GIF, or short WebM
that is intended for the PR. Wrap returned image URLs as links so reviewers can
open the full-size asset. If the upload tool is unavailable, use the repository's
`pr-assets` GitHub release and namespace filenames by PR number.

The PR description should include a section like:

```markdown
## Demo

<!-- pr-screenshot: captured at abc1234 -->

| Before | After |
| --- | --- |
| [![before](BEFORE_URL)](BEFORE_URL) | [![after](AFTER_URL)](AFTER_URL) |

[Interaction demo](VIDEO_URL)
```

Use a single image when no baseline exists. Do not embed a raw `<video>` tag;
GitHub renders GIF plus a download link more consistently.

## Cleanup

Remove the temporary spec and local screenshot output after assets are uploaded:

```bash
rm -f web/e2e/tests/_pr-screenshot.spec.ts
rm -rf /tmp/pr-screenshots
```

Do not commit the temporary spec, generated screenshots, videos, or detached
worktree. Keep only the checked-in Playwright config, package manifest/lockfile,
and this skill.

## Tips

- Use viewport captures for bounded features and `fullPage: true` only when page-level context matters.
- Add a 390x844 capture when the change is responsive or mobile-specific.
- Capture every changed UI area when a PR touches multiple routes or components.
- Skip evidence for purely structural changes such as test-id or class-name renames with no visual effect.
139 changes: 139 additions & 0 deletions .claude/skills/start-app-server/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
name: start-app-server
description: "Start the django-react-intro web app in a Docker container and validate it responds. Uses a pre-validated recipe with no discovery or guessing. Triggers on: 'spin up the server', 'run my app', 'start the server', 'verify app server', 'test my server', 'run app server', 'spin up the app'."
user_invocable: true
codepress_generated: true
---

# Start App Server — django-react-intro

Fast-path skill for starting the public Create React App frontend in Docker.
The recipe was bootstrapped on 2026-08-06T20:07:26Z and lives at
`.codepress/start-app-server/recipe.json`.

## Tools

Use these tools directly:

- `build_and_start_app_server` — build the image and start the container
- `forward_app_request` — send an HTTP request into the container
- `get_app_server_logs` — inspect stdout and stderr
- `stop_app_server` — stop a container when cleanup is requested

## Static Context

- **Stack**: Create React App 1.0.10 with React 15, served by the legacy CRA development server
- **Dockerfile**: `Dockerfile.codepress`
- **Port**: 3000
- **Validation**: `GET /` → status in `[200, 301, 302, 404]`
- **Services**: none
- **Required secrets**: none
- **Container alias**: `web`

`server/` contains a separate Django 1.11 project, but this recipe deliberately
targets the frontend because it is the repository's existing runnable preview
surface. The existing `.codepress/dev-server/Dockerfile.web` is a Live Dev
Server hydration image and is not used by this standalone app-server recipe.

## Step 1: Drift Check

Compare the current inputs with the recipe before starting:

```bash
git hash-object Dockerfile.codepress # expected prefix e5c74266935e1269
git hash-object web/package.json # expected prefix 82f2e0338f40d5f8
```

If either checksum differs, continue with the build but report that the recipe
may need to be regenerated after the run.

## Step 2: Environment

This app needs no vault secrets. Use these safe runtime values; they are also
set in the Dockerfile:

```json
{
"HOST": "0.0.0.0",
"BROWSER": "none",
"DANGEROUSLY_DISABLE_HOST_CHECK": "true"
}
```

## Step 3: Build and Start

```text
build_and_start_app_server(
workspaceDir=<absolute path to the repository root>,
port=3000,
dockerfilePath="Dockerfile.codepress",
envVars={
"HOST": "0.0.0.0",
"PORT": "3000",
"BROWSER": "none",
"DANGEROUSLY_DISABLE_HOST_CHECK": "true"
},
name="web"
)
```

The image installs Yarn 1.22.22 and all dependencies from `web/yarn.lock` at
build time. The foreground command invokes the installed CRA start script with
Node 18, which is required by the repository's legacy `websocket-driver`
dependency.

If retrying after a Dockerfile fix, pass `existingContainerId=<previous id>` so
the previous container is stopped during the successful rebuild.

## Step 4: Validate

If the build tool reports `health_check: "ready"`, send the validation request:

```text
forward_app_request(
containerId=<container id>,
path="/",
method="GET"
)
```

Success is any response status in `[200, 301, 302, 404]`. If health is timed
out, poll the same request up to 12 times at five-second intervals. If it still
does not respond, inspect `get_app_server_logs` before deciding whether the
container needs a repair.

## Step 5: Report

Report the container ID, port 3000, the validation response, and these commands:

```text
forward_app_request(containerId=<container id>, path="/")
stop_app_server(containerId=<container id>)
```

When this skill is called only to start the server, leave a healthy container
running. A caller performing verification owns cleanup after its contract run.

## Known Fixes

- The pre-existing `.codepress/dev-server/Dockerfile.web` is intended for Live Dev Server hydration and has no source/dependency copy; use `Dockerfile.codepress` for standalone app-server runs.
- CRA 1.0.10 and `websocket-driver` require Node 18 because newer Node versions removed the `http_parser` binding.
- The official Node 18 image provides a Corepack Yarn shim, so the Dockerfile pins Yarn 1.22.22 with `corepack prepare`.

## Repair on Failure

If the build or start fails, inspect the full build error and container logs,
identify all root causes, and make one combined Dockerfile correction before
rebuilding. Keep the dependency install inside the image; never rely on host
`node_modules` or download packages from the container's startup command.

The recipe's `repair_count` has a ceiling of three. If it is already at least
three, stop repairing and ask for a fresh bootstrap. After a successful repair,
increment `repair_count`, set `origin` to `repair`, update `bootstrapped_at`,
recompute the two input checksums, and append the fix to `known_fixes`.

## Cleanup

Leave a successfully started container running unless the caller explicitly
asks for teardown or started it only for a verification run. Stop failed or
orphaned containers with `stop_app_server(containerId=<container id>)`.
Loading