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
214 changes: 214 additions & 0 deletions .agents/skills/pr-screenshots/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
---
name: pr-screenshots
description: "Capture, annotate, and include screenshots in pull requests for UI changes. Use when creating or updating PRs that touch agents-manage-ui components/pages, agents-docs content, or any web-facing surface. Also use when asked to add before/after screenshots, visual diffs, preview deployment links, or enrich PR descriptions. Triggers on: PR screenshots, before/after, visual diff, PR description, preview deployment, capture screenshot, PR images, enrich PR."
---

# PR Screenshots

Capture, redact, annotate, and embed screenshots in GitHub PRs for UI changes.

## When to use

- Creating/updating PRs touching `agents-manage-ui/src/components/**`, `agents-manage-ui/src/app/**`, or `agents-docs/content/**`
- User asks for screenshots, before/after comparisons, or PR body enrichment
- Skip for backend-only, test-only, or non-visual changes

## Workflow

1. **Identify affected routes** from the diff — see [affected-routes.md](references/affected-routes.md)
2. **Capture screenshots** — run `scripts/capture.ts`
3. **Validate no sensitive data** — run `scripts/validate-sensitive.ts`
4. **Annotate** — run `scripts/annotate.ts` (labels, borders, side-by-side)
5. **Upload & embed** — update PR body with images and preview links

---

## Step 1: Identify Affected Pages

Analyze the diff to determine which UI routes are impacted. Use the mapping in [references/affected-routes.md](references/affected-routes.md).

Example: changes to `components/agent/sidepane/nodes/model-selector.tsx` affect the **agent editor** at `/{tenantId}/projects/{projectId}/agents/{agentId}`.

If the diff only touches backend code, tests, or non-visual files, skip screenshot capture.

---

## Step 2: Capture Screenshots

### Environment setup

| Environment | Base URL | How to start |
|---|---|---|
| **Local dev** | `http://localhost:3000` | `cd agents-manage-ui && pnpm dev` |
| **Vercel preview** | `https://agents-git-{branch}-inkeep.vercel.app` | Automatic on PR push |
| **Playwright server** | Connect via `--connect ws://localhost:3001` | See "Reusable server" below |

### Capture command

```bash
# Local dev
npx tsx .cursor/skills/pr-screenshots/scripts/capture.ts \
--base-url http://localhost:3000 \
--routes "/{tenantId}/projects/{projectId}/agents/{agentId}" \
--output-dir ./pr-screenshots

# Vercel preview
npx tsx .cursor/skills/pr-screenshots/scripts/capture.ts \
--base-url https://agents-git-my-branch-inkeep.vercel.app \
--routes "/{tenantId}/projects/{projectId}/agents/{agentId}" \
--output-dir ./pr-screenshots

# With Playwright server (reuses browser across captures)
npx tsx .cursor/skills/pr-screenshots/scripts/capture.ts \
--connect ws://localhost:3001 \
--base-url http://localhost:3000 \
--routes "/t1/projects/p1/agents/a1,/t1/projects/p1/settings" \
--output-dir ./pr-screenshots
```

### All capture options

| Option | Default | Description |
|---|---|---|
| `--base-url <url>` | *required* | Target URL (local dev or preview) |
| `--routes <paths>` | *required* | Comma-separated route paths |
| `--output-dir <dir>` | `./pr-screenshots` | Where to save PNGs and DOM text |
| `--viewport <WxH>` | `1280x800` | Browser viewport size |
| `--connect <ws-url>` | — | Connect to existing Playwright server |
| `--mask-selectors <s>` | — | Additional CSS selectors to blur |
| `--wait <ms>` | `2000` | Wait after page load before capture |
| `--full-page` | `false` | Capture full scrollable page |
| `--auth-cookie <value>` | — | Session cookie for authenticated pages |

### Reusable Playwright server

Start a server once, reuse across multiple captures:

```bash
# Terminal 1: start server
npx tsx .cursor/skills/pr-screenshots/scripts/capture.ts --serve --port 3001

# Terminal 2+: connect and capture
npx tsx .cursor/skills/pr-screenshots/scripts/capture.ts \
--connect ws://localhost:3001 --base-url http://localhost:3000 \
--routes "/..." --output-dir ./pr-screenshots
```

### Using browser-use subagent (Cursor alternative)

When scripts are unavailable, use the `browser-use` subagent:
1. Navigate to the target URL
2. Call `browser_screenshot` to capture
3. Download the image for annotation

---

## Step 3: Validate Sensitive Data

**Always run before uploading to GitHub.**

```bash
npx tsx .cursor/skills/pr-screenshots/scripts/validate-sensitive.ts \
--dir ./pr-screenshots
```

The script checks `.dom-text.txt` files (saved by capture) for:
- API keys (`sk-`, `ik_`, `sk-ant-`, `AKIA`, `sk_live_`)
- Tokens (Bearer, JWT, GitHub PATs)
- PEM private keys
- Connection strings with credentials
- Email addresses
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MINOR: Documentation lists "Email addresses" but script doesn't check for them

Issue: The documentation states the validation script checks for "Email addresses" but validate-sensitive.ts has no email pattern in its SENSITIVE_PATTERNS array.

Why: Documentation should accurately reflect what the script does. Users relying on email detection would be surprised to find it's not implemented.

Fix: Either remove "Email addresses" from this list, or add the pattern to validate-sensitive.ts:

{ name: 'Email address', pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, severity: 'warning' },

Refs:


Exit code 1 = sensitive data found. Re-capture with additional `--mask-selectors` or fix the source before proceeding.

### Pre-capture masking (automatic)

The capture script automatically masks these before taking screenshots:

| Selector | What it catches |
|---|---|
| `input[type="password"]` | Password fields |
| `input[name="apiKeyToSet"]` | Credential API key inputs |
| `input[data-field="value"]` | Header value inputs (GenericKeyValueInput) |
| `[role="alertdialog"] pre` | API key display dialogs |
| Text matching `sk-`, `ik_`, `Bearer`, `eyJ`, `ghp_`, PEM headers | In-page tokens/keys |

Add more with `--mask-selectors "selector1,selector2"`.

### What to check manually

- Screenshots of the **credentials** page (`/credentials/**`)
- Screenshots of the **API keys** page (`/api-keys`)
- Any page where users enter secrets (trigger auth headers, MCP server config)

---

## Step 4: Annotate Images

```bash
# Add "Before" label with red border
npx tsx .cursor/skills/pr-screenshots/scripts/annotate.ts \
--input before.png --label "Before" --border "#ef4444" --output before-labeled.png

# Add "After" label with green border
npx tsx .cursor/skills/pr-screenshots/scripts/annotate.ts \
--input after.png --label "After" --border "#22c55e" --output after-labeled.png

# Side-by-side comparison
npx tsx .cursor/skills/pr-screenshots/scripts/annotate.ts \
--stitch before.png after.png --labels "Before,After" --output comparison.png
```

---

## Step 5: Upload & Embed in PR

### Upload images to GitHub

Images in PR markdown need permanent URLs. Use one of:

**Option A — PR comment with image** (simplest):
```bash
# GitHub renders attached images with permanent CDN URLs
gh pr comment {pr-number} --body "![Before](./pr-screenshots/before-labeled.png)"
```

**Option B — Update PR body directly**:
```bash
gh pr edit {pr-number} --body "$(cat pr-body.md)"
```

### PR body templates

Use the templates in [references/pr-templates.md](references/pr-templates.md) for consistent formatting. Include:

1. **Visual Changes** section with before/after screenshots
2. **Test URLs** section with links to preview deployment pages
3. **Summary** of what changed and why

### Generating preview URLs

Pattern: `https://agents-git-{branch}-inkeep.vercel.app/{tenantId}/projects/{projectId}/...`

Replace `{branch}` with the PR branch name (hyphens, not slashes). Use the affected-routes mapping to build the full paths.

---

## Dependencies

These scripts require packages available in the monorepo:

| Package | Source | Notes |
|---|---|---|
| `playwright` | `agents-manage-ui` workspace dep | Browser automation |
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MINOR: Incorrect dependency source for playwright

Issue: This table states playwright is an agents-manage-ui workspace dep, but this PR adds it directly to root devDependencies in package.json.

Why: Documentation should accurately reflect where dependencies come from. Users looking here for troubleshooting may be confused.

Fix:

Suggested change
| `playwright` | `agents-manage-ui` workspace dep | Browser automation |
| `playwright` | Root dev dep | Browser automation |

Refs:

| `sharp` | Root dev dep | Image annotation |
| `tsx` | Used in root scripts | TypeScript runner |

If `sharp` is not installed: `pnpm add -Dw sharp`

---

## Additional Resources

- [references/affected-routes.md](references/affected-routes.md) — File path → UI route mapping
- [references/pr-templates.md](references/pr-templates.md) — PR body markdown templates
166 changes: 166 additions & 0 deletions .agents/skills/pr-screenshots/references/affected-routes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Affected Routes Mapping

Map changed files to the UI routes they affect. Use this to determine which pages to screenshot.

## How to use

1. Look at the files changed in the PR diff
2. Find the matching file pattern(s) below
3. The corresponding route is the page to screenshot
4. Replace `{tenantId}`, `{projectId}`, etc. with real IDs from the target environment

## Component → Route Mapping

### Agent Visual Builder

| File pattern | Route | Description |
|---|---|---|
| `components/agent/nodes/**` | `/{tenantId}/projects/{projectId}/agents/{agentId}` | Agent graph nodes (agent, MCP, sub-agent) |
| `components/agent/edges/**` | `/{tenantId}/projects/{projectId}/agents/{agentId}` | Agent graph edges/connections |
| `components/agent/sidepane/**` | `/{tenantId}/projects/{projectId}/agents/{agentId}` | Side panel editors (model, prompt, tools) |
| `components/agent/sidepane/nodes/model-*` | `/{tenantId}/projects/{projectId}/agents/{agentId}` | Model selector, model section |
| `components/agent/sidepane/metadata/**` | `/{tenantId}/projects/{projectId}/agents/{agentId}` | Agent metadata editor |
| `components/agent/toolbar/**` | `/{tenantId}/projects/{projectId}/agents/{agentId}` | Agent toolbar (save, deploy, etc.) |
| `components/agent/playground/**` | `/{tenantId}/projects/{projectId}/agents/{agentId}` | In-editor chat playground |
| `components/agent/copilot/**` | `/{tenantId}/projects/{projectId}/agents/{agentId}` | Agent copilot panel |
| `features/agent/**` | `/{tenantId}/projects/{projectId}/agents/{agentId}` | Agent domain logic, state, commands |

### Agent List

| File pattern | Route | Description |
|---|---|---|
| `components/agents/**` | `/{tenantId}/projects/{projectId}/agents` | Agent list page |

### Project Settings

| File pattern | Route | Description |
|---|---|---|
| `components/projects/form/**` | `/{tenantId}/projects/{projectId}/settings` | Project settings form |
| `components/projects/form/project-models-section.*` | `/{tenantId}/projects/{projectId}/settings` | Project model configuration |
| `features/project/**` | `/{tenantId}/projects/{projectId}/settings` | Project state |

### Projects List

| File pattern | Route | Description |
|---|---|---|
| `components/projects/**` (not `form/`) | `/{tenantId}/projects` | Projects list page |

### Credentials

| File pattern | Route | Description |
|---|---|---|
| `components/credentials/**` | `/{tenantId}/projects/{projectId}/credentials` | Credentials list |
| `components/credentials/views/**` | `/{tenantId}/projects/{projectId}/credentials/new/bearer` | New credential form |

### API Keys

| File pattern | Route | Description |
|---|---|---|
| `components/api-keys/**` | `/{tenantId}/projects/{projectId}/api-keys` | API keys page |

### MCP Servers

| File pattern | Route | Description |
|---|---|---|
| `components/mcp-servers/**` | `/{tenantId}/projects/{projectId}/mcp-servers` | MCP server list |
| `components/mcp-servers/form/**` | `/{tenantId}/projects/{projectId}/mcp-servers/{mcpServerId}/edit` | MCP server edit form |
| `components/mcp-servers/selection/**` | `/{tenantId}/projects/{projectId}/mcp-servers/new` | MCP server selection |

### External Agents

| File pattern | Route | Description |
|---|---|---|
| `components/external-agents/**` | `/{tenantId}/projects/{projectId}/external-agents` | External agent list |
| `components/external-agents/form/**` | `/{tenantId}/projects/{projectId}/external-agents/new` | External agent form |

### Triggers

| File pattern | Route | Description |
|---|---|---|
| `components/triggers/**` | `/{tenantId}/projects/{projectId}/agents/{agentId}/triggers` | Triggers list |
| `components/triggers/trigger-form*` | `/{tenantId}/projects/{projectId}/agents/{agentId}/triggers/new` | New/edit trigger form |

### Data Components

| File pattern | Route | Description |
|---|---|---|
| `components/data-components/**` | `/{tenantId}/projects/{projectId}/components` | Data component list |
| `components/data-components/form/**` | `/{tenantId}/projects/{projectId}/components/new` | Data component form |

### Artifact Components

| File pattern | Route | Description |
|---|---|---|
| `components/artifact-components/**` | `/{tenantId}/projects/{projectId}/artifacts` | Artifact list |
| `components/artifact-components/form/**` | `/{tenantId}/projects/{projectId}/artifacts/new` | Artifact form |

### Datasets & Evaluations

| File pattern | Route | Description |
|---|---|---|
| `components/datasets/**` | `/{tenantId}/projects/{projectId}/datasets` | Dataset list |
| `components/dataset-items/**` | `/{tenantId}/projects/{projectId}/datasets/{datasetId}` | Dataset item view |
| `components/evaluations/**` | `/{tenantId}/projects/{projectId}/evaluations` | Evaluations page |
| `components/evaluation-run-configs/**` | `/{tenantId}/projects/{projectId}/evaluations/run-configs/{configId}` | Run config results |
| `components/evaluation-jobs/**` | `/{tenantId}/projects/{projectId}/evaluations/jobs/{configId}` | Evaluation job results |

### Traces

| File pattern | Route | Description |
|---|---|---|
| `components/traces/**` | `/{tenantId}/projects/{projectId}/traces` | Traces overview |
| `hooks/use-traces*` | `/{tenantId}/projects/{projectId}/traces` | Traces data hooks |

### Organization Settings

| File pattern | Route | Description |
|---|---|---|
| `components/settings/**` | `/{tenantId}/settings` | Org settings |
| `components/access/**` | `/{tenantId}/projects/{projectId}/members` | Project members |

### Shared / Cross-cutting

| File pattern | Route | Description |
|---|---|---|
| `components/shared/model-configuration.*` | Multiple: agent editor + project settings | Model config (appears in both) |
| `components/form/**` | Multiple | Generic form components, affect all forms |
| `components/editors/**` | Multiple | JSON/prompt editors, affect agent + project pages |
| `components/ui/**` | Multiple | Base UI primitives, affects everything |
| `components/layout/**` | Multiple | Page headers, empty states |
| `components/errors/**` | Multiple | Error pages |
| `components/icons/**` | Multiple | Icons |

### Auth Pages

| File pattern | Route | Description |
|---|---|---|
| `app/login/**` | `/login` | Login page |
| `app/reset-password/**` | `/reset-password` | Password reset |
| `app/accept-invitation/**` | `/accept-invitation/{invitationId}` | Invitation acceptance |

### Documentation (agents-docs)

| File pattern | Route | Description |
|---|---|---|
| `agents-docs/content/**` | Preview docs site | Documentation pages |
| `agents-docs/_snippets/**` | Preview docs site | Reusable snippets |
| `agents-docs/public/images/**` | Preview docs site | Documentation images |

## Pages with Sensitive Data (extra caution)

These pages may display or accept sensitive information. Always verify masking works correctly:

| Route | Sensitive content |
|---|---|
| `/{tenantId}/projects/{projectId}/credentials/**` | API keys, OAuth secrets, private keys |
| `/{tenantId}/projects/{projectId}/api-keys` | API key prefixes, new key display |
| `/{tenantId}/projects/{projectId}/agents/{agentId}/triggers/*/edit` | Auth header values |
| `/{tenantId}/projects/{projectId}/mcp-servers/*/edit` | Server URLs with credentials |

## Vercel Preview URL Pattern

```
https://agents-git-{branch-name}-inkeep.vercel.app{route}
```

Replace `{branch-name}` with the PR branch (use hyphens, e.g., `bugfix/azure-model-selector` becomes `bugfix-azure-model-selector`).
Loading