Skip to content
Back to skills

Code Review

ASecurity

Review a pull request in the mendix/web-widgets monorepo. Checks Mendix widget conventions, React/MobX patterns, versioning, test coverage, Atlas UI styling, security, and accessibility.

  • 40 stars
  • 0 votes
  • 1 copy
  • 12 views
  • Added June 5, 2026
developmentjavascriptrustgojavareactnodeawscode-reviewgitapi

Works with

  • claude code
  • terminal
  • cli
  • api

Security analysis

A100/100

Scanned June 5, 2026

npx -y skills add mendix/web-widgets --skill code-review --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Code Review?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Code Review
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mendix-code-review/badge)](https://www.skillsdirectory.com/skills/mendix-code-review)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: code-review
description: Review a pull request in the mendix/web-widgets monorepo. Checks Mendix widget conventions, React/MobX patterns, versioning, test coverage, Atlas UI styling, security, and accessibility.
---

# Code Review

Review the PR diff against the standards in this repository. Read `AGENTS.md` for full repo context.

**Before reviewing anything, run:**

```sh
gh pr diff --name-only   # build the exact list of changed files — only read these
gh pr checks             # surface any failing CI checks before reading code
```

Only read files that appear in the diff output.

## What to check on every PR

### PR metadata

- **Title**: JIRA format `[XX-000]: description` or conventional commits (`feat:`, `fix:`, etc.)
- **Template adherence**: lint/test run locally, new tests added, related PRs linked
- **Multi-package PRs**: validate each changed package separately

### CI checks

Review the `gh pr checks` output before reading any files:

- **Lint failing** → Medium finding — paste the relevant error lines in the finding
- **Unit tests failing** → Medium finding — note the package and failing test file
- **E2E tests failing** → Medium finding — note the test name and failure reason
- **All checks passing** → add "All CI checks passed ✅" to the summary comment

### Changelog (per changed package)

Version bumps happen in a separate dedicated PR — do not require or flag missing semver bumps.

If runtime code, public API, XML schema, or behavior changed:

- Require `CHANGELOG.md` entry (Keep a Changelog format)
- Suggest: `pnpm -w changelog`

If refactor/docs/tests-only: changelog entry not required — confirm with author.

### Mendix-specific

`AGENTS.md` covers the core rules (canExecute, loading states, lowerCamelCase XML keys). Flag these additional review issues:

- XML changed but TS props not updated, or widget ID is not unique
- `EditableValue` read without checking `.status` — can render stale/undefined data
- `ActionValue.execute()` called without checking `.canExecute` first

### React

Flag these patterns — general React conventions are assumed known:

- Missing or wrong `useEffect`/`useMemo`/`useCallback` deps; stale closures
- Async effect sets state without a cleanup guard:
    ```ts
    useEffect(() => {
        let active = true;
        (async () => {
            const data = await fetchData();
            if (active) setState(data);
        })();
        return () => {
            active = false;
        };
    }, [fetchData]);
    ```
- Array index used as list `key` — requires a stable unique key
- Props spread onto DOM nodes (`<div {...props}>`) — strips unknown HTML attributes

### MobX

- `makeAutoObservable` or `makeObservable` missing from store constructor
- State mutation outside `action` — suggest `runInAction` or `action` wrapper
- `computed` with side effects — must be pure
- Missing `observer` HOC or `useSubscribe()` from `@mendix/widget-plugin-mobx-kit` on React components that read MobX state

### Styling

Conventions are in `docs/requirements/frontend-guidelines.md`. Flag only deviations:

- Inline styles used for static design
- Core Atlas UI classes overridden
- `!important` used
- CSS class not prefixed with widget name

### Unit tests

Files live in `src/**/__tests__/*.spec.ts(x)` and run with Jest + RTL (enzyme-free).

**Structure**

- Use `describe`/`it` blocks; group related cases under a nested `describe`
- Define a `defaultProps` constant and a factory render helper to avoid repetition:
    ```ts
    const defaultProps: MyWidgetProps = { ... };
    const renderWidget = (props = defaultProps) => render(<MyWidget {...props} />);
    ```

**Mendix data mocking — always use builders, never manual objects**

```ts
import { EditableValueBuilder, ListValueBuilder, actionValue, obj } from "@mendix/widget-plugin-test-utils";

const value = new EditableValueBuilder<string>().withValue("hello").build();
const readOnly = new EditableValueBuilder<string>().withValue("x").isReadOnly().build();
const loading = new EditableValueBuilder<string>().isLoading().build();
const list = new ListValueBuilder().withItems([obj("A"), obj("B")]).build();
const action = actionValue(); // jest.fn() — assert with .execute toHaveBeenCalled()
```

**What to cover**

- All Mendix data states: `Available`, `Loading`, `Unavailable`, `ReadOnly`
- All prop/behavior branches (null checks, conditional renders, edge cases)
- User interactions via `fireEvent` or `userEvent`
- Verify `setValue` / `execute` calls: `expect(action.execute).toHaveBeenCalled()`
- Accessibility assertions: `getByRole`, `getByLabelText`, ARIA attributes

**What to flag**

- Manual mock objects instead of builders — brittle and miss status edge cases
- Enzyme patterns (`shallow`, `mount`, `instance()`) — must use RTL
- Snapshot tests on dynamic content (dates, IDs, async state) — use specific assertions instead
- Missing `afterEach` mock cleanup — causes test pollution
- Missing `ResizeObserver` / `window.mx` global setup when widget requires it (add to `jest.setup.ts`)
- Jest config not extending `@mendix/pluggable-widgets-tools/test-config/jest.config`

### E2E tests

Files live in `e2e/*.spec.js` and run with Playwright (Chromium). Config inherits from `@mendix/run-e2e/playwright.config.cjs`.

**Mandatory structure — every file must have this**

```js
import { test, expect } from "@playwright/test";

test.afterEach("Cleanup session", async ({ page }) => {
    await page.evaluate(() => window.mx.session.logout());
});

test.describe("WidgetName", () => {
    test.beforeEach(async ({ page }) => {
        await page.goto("/");
        await page.waitForLoadState("networkidle");
    });
});
```

**Selectors — in order of preference**

1. `.mx-name-*` — Mendix widget names (most stable): `page.locator(".mx-name-myWidget")`
2. ARIA roles: `page.getByRole("button", { name: "Save" })`
3. Widget CSS classes: `page.locator(".widget-badge-button-text")`
4. Avoid brittle selectors: nth-child chains, deeply nested CSS, text-only locators

**Assertions**

```js
await expect(page.locator(".mx-name-myWidget")).toBeVisible();
await expect(page.locator(".badge")).toContainText("New");
await expect(page.locator(".mx-name-myWidget")).toHaveScreenshot("myWidget-default.png");
```

**Accessibility scanning (use for new interactive widgets)**

```js
import AxeBuilder from "@axe-core/playwright";
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
```

**What to flag**

- Missing `afterEach` session logout — will exceed Mendix's 5-session license limit in CI
- `page.waitForTimeout()` / hardcoded `sleep` — replace with `waitForLoadState` or a Playwright locator assertion
- Selectors that don't use `.mx-name-*` when a Mendix widget name is available
- Screenshot baselines not committed — `toHaveScreenshot` requires a baseline PNG in the repo
- Missing `page.waitForLoadState("networkidle")` in `beforeEach` — causes flaky tests
- E2E file not following `WidgetName.spec.js` naming convention

### Security

- **No `dangerouslySetInnerHTML`** unless input is sanitized with a trusted library (e.g. DOMPurify); flag any unsanitized usage as high severity
- **No secrets or tokens** hardcoded in source — API keys, client IDs, URLs with credentials
- **Safe external data handling**: validate and sanitize data from Mendix props before rendering or passing to DOM; guard against prototype pollution when merging objects
- **No `eval()` or `new Function()`** with dynamic input
- **Third-party scripts**: dynamic `<script>` injection must be reviewed for XSS risk
- **URL handling**: verify `href`/`src` values derived from user input are validated (guard against `javascript:` and `data:` URIs)
- **Event handlers**: avoid attaching global `window`/`document` listeners without cleanup — can leak references and be exploited

### Accessibility

Full requirements are in `docs/requirements/frontend-guidelines.md`. Flag only deviations:

- Interactive element is a `<div>` or `<span>` with `onClick` — replace with `<button>` or `<a>`
- Icon-only button missing `aria-label`
- `<img>` missing `alt` attribute (use `alt=""` for decorative)
- Dialog/modal does not trap focus or restore it on close — use `FloatingFocusManager`
- Colour used as the sole differentiator — pair with text or icon

## Heuristics

| Situation                                      | Severity | Comment                                                  |
| ---------------------------------------------- | -------- | -------------------------------------------------------- |
| Code/XML changed, no CHANGELOG entry           | Medium   | `pnpm -w changelog` — version bumps are a separate PR    |
| CI check failing (lint / unit / E2E)           | Medium   | Paste the error; do not approve until green or explained |
| Feature/fix without tests                      | Medium   | Add unit tests; consider E2E for interactive behaviour   |
| `dangerouslySetInnerHTML` without sanitization | High     | Require DOMPurify or equivalent before merge             |
| Hardcoded secret, token, or credential         | Critical | Must be removed and rotated immediately                  |
| `javascript:` or `data:` URI from user input   | High     | Validate before use — XSS risk                           |

## Scope

### What to review

| Path pattern                                                   | What to check                                                                      |
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `packages/pluggableWidgets/*/src/**/*.{ts,tsx}`                | Widget logic, React hooks, MobX, Mendix data API usage                             |
| `packages/pluggableWidgets/*/*.xml`                            | Widget manifest: property keys (lowerCamelCase), unique ID, XML ↔ TS alignment     |
| `packages/pluggableWidgets/*/**/*.scss`                        | Styling: BEM naming, Atlas UI classes, no `!important`, no inline styles           |
| `packages/pluggableWidgets/*/src/**/__tests__/*.spec.{ts,tsx}` | Unit test coverage, builder usage, RTL patterns                                    |
| `packages/pluggableWidgets/*/e2e/*.spec.js`                    | E2E structure, selectors, afterEach logout, no hardcoded waits                     |
| `packages/pluggableWidgets/*/package.json`                     | Version bumps happen in a separate PR — do not flag missing bumps                  |
| `packages/pluggableWidgets/*/CHANGELOG.md`                     | Keep a Changelog entry present when runtime/XML/behavior changed                   |
| `packages/pluggableWidgets/*/*.editorConfig.ts`                | Studio Pro design-time config aligns with XML properties                           |
| `packages/pluggableWidgets/*/*.editorPreview.tsx`              | Preview component renders without crashing; no production-only imports             |
| `packages/shared/*/src/**/*.{ts,tsx}`                          | Shared utility changes — check for breaking API changes affecting widget consumers |
| `packages/modules/*/src/**/*.{ts,tsx}`                         | Module-level logic and Mendix integration patterns                                 |
| `automation/**/*.{ts,mjs,cjs}`                                 | Build/test automation scripts — no destructive ops, no hardcoded paths             |
| `.github/workflows/*.yml`                                      | See workflow rules below                                                           |
| `.claude/skills/**`                                            | See skill rules below                                                              |

### What to ignore

- `dist/**` — build output, never review
- `pnpm-lock.yaml` — lockfile-only changes need no review unless paired with `package.json` changes
- `**/.turbo/**`, `**/node_modules/**` — generated/cached artifacts
- `**/test-results/**`, `**/results/**` — test output directories
- `dist/tmp/**` — intermediate build artifacts
- `**/__snapshots__/**` — auto-generated snapshots (flag only if snapshot was deleted without test change)
- `*.mpk` — compiled Mendix packages

### Multi-package PRs

When a PR touches multiple packages, validate each changed package separately:

- CHANGELOG entry per package (version bumps are out of scope)
- XML ↔ TS alignment per widget
- Test coverage per changed component

### GitHub Actions workflows (`.github/workflows/**`)

- All action references must be SHA-pinned with a version comment: `uses: actions/foo@<sha> # vX.Y`
- Secrets must be read from `${{ secrets.* }}` — never hardcoded values
- Permissions must be minimal: only declare what the job actually needs
- Jobs that assume AWS/cloud roles must have `id-token: write` and use OIDC (`aws-actions/configure-aws-credentials`)
- Jobs running on `pull_request` events should guard against fork PRs: check `head.repo.full_name` or `github.repository`
- Jobs triggerable by comments (`issue_comment`) must restrict by `author_association` to prevent external contributors from triggering privileged workflows
- Every long-running job should have `timeout-minutes` set
- Shared `concurrency` groups with `cancel-in-progress: true` will cancel in-flight jobs — use per-job concurrency for interactive workflows

### Claude Code skills (`.claude/skills/**`)

- Frontmatter (`name`, `description`) must be present and accurate
- Instructions should be actionable and specific — vague guidance leads to inconsistent reviews
- Code examples must match the patterns actually used in this repo (check against `src/**` if unsure)
- New sections should include a "What to flag" list of concrete anti-patterns

## Output format

**Determine the execution context first** by checking whether the `CI` environment variable is set:

- **CI environment** (`CI=true`): post the review as a PR comment using `gh`, following the template below.
- **Local environment** (`CI` unset or empty): print the review directly to the terminal in the same format. Do NOT post any `gh` comment.

Use inline comments for issues that reference a specific line; use the summary comment/output for file-level or cross-cutting issues.

### Summary comment template

**Verdict line** — always the first line, one of:

- `✅ Approved — no issues found`
- `⚠️ Approved with suggestions — low-severity items only, safe to merge`
- `🔶 Changes requested — one or more medium-severity items must be addressed`
- `🚨 Blocked — high-severity issue (security, data loss, broken API) must be fixed`

**Post the comment using exactly this structure** (GitHub Markdown — render as-is, do not wrap in a code fence):

The comment must start with:

```
## AI Code Review
```

Then a blockquote verdict on the next line:

```
> ✅ Approved — no issues found
```

(replace with the appropriate verdict emoji and text)

Then a horizontal rule `---`, then a `### What was reviewed` section with a Markdown table:

```
### What was reviewed

| File | Change |
| --- | --- |
| `path/to/file.ts` | Brief description of what changed |
| `path/to/other.yml` | Brief description |

Skipped (out of scope): `dist/`, `pnpm-lock.yaml`
```

Then `---`, then a `### Findings` section (omit entirely on a clean PR) where each finding is a level-4 heading:

```
### Findings

#### 🚨 High — <short title>

**File:** `path/to/file.ts` line 42
**Problem:** What is wrong and why it matters.
**Fix:**
\`\`\`ts
// suggested fix snippet
\`\`\`

---

#### 🔶 Medium — <short title>

**File:** `path/to/file.ts` line 10
**Problem:** What is wrong and why it matters.
**Fix:** What to do (prose or snippet).

---

#### ⚠️ Low — <short title>

**File:** `path/to/file.ts` line 5
**Note:** What to consider, not blocking.
```

Then `---`, then a `### Positives` section as a bullet list:

```
### Positives

- Specific thing done well (not generic praise)
- Another concrete positive
```

### Rules

- **The summary comment is mandatory** — even on a clean PR. It proves the review ran.
- Omit the **Findings** section entirely on a clean PR; do not write "no findings".
- Each finding must include: severity emoji, a short title, the file + line, the problem, and a concrete fix.
- Keep **Positives** specific — name what was done right (e.g. "SHAs are pinned with version comments"), not generic ("good job").
- One finding per `####` block — do not bundle multiple issues.
- Be specific and actionable; avoid noise.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.