with one click
code-review
// 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.
// 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.
| 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. |
Review the PR diff against the standards in this repository. Read AGENTS.md for full repo context.
[XX-000]: description or conventional commits (feat:, fix:, etc.)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:
CHANGELOG.md entry (Keep a Changelog format)pnpm -w changelogIf refactor/docs/tests-only: changelog entry not required — confirm with author.
AGENTS.md covers the core rules (canExecute, loading states, lowerCamelCase XML keys). Flag these additional review issues:
EditableValue read without checking .status — can render stale/undefined dataActionValue.execute() called without checking .canExecute firstFlag these patterns — general React conventions are assumed known:
useEffect/useMemo/useCallback deps; stale closuresuseEffect(() => {
let active = true;
fetchData().then(data => {
if (active) setState(data);
});
return () => {
active = false;
};
}, [fetchData]);
key — requires a stable unique key<div {...props}>) — strips unknown HTML attributesmakeAutoObservable or makeObservable missing from store constructoraction — suggest runInAction or action wrappercomputed with side effects — must be pureobserver HOC or useSubscribe() from @mendix/widget-plugin-mobx-kit on React components that read MobX stateConventions are in docs/requirements/frontend-guidelines.md. Flag only deviations:
!important usedFiles live in src/**/__tests__/*.spec.ts(x) and run with Jest + RTL (enzyme-free).
Structure
describe/it blocks; group related cases under a nested describedefaultProps constant and a factory render helper to avoid repetition:
const defaultProps: MyWidgetProps = { ... };
const renderWidget = (props = defaultProps) => render(<MyWidget {...props} />);
Mendix data mocking — always use builders, never manual objects
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
Available, Loading, Unavailable, ReadOnlyfireEvent or userEventsetValue / execute calls: expect(action.execute).toHaveBeenCalled()getByRole, getByLabelText, ARIA attributesWhat to flag
shallow, mount, instance()) — must use RTLafterEach mock cleanup — causes test pollutionResizeObserver / window.mx global setup when widget requires it (add to jest.setup.ts)@mendix/pluggable-widgets-tools/test-config/jest.configFiles 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
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
.mx-name-* — Mendix widget names (most stable): page.locator(".mx-name-myWidget")page.getByRole("button", { name: "Save" })page.locator(".widget-badge-button-text")Assertions
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)
import AxeBuilder from "@axe-core/playwright";
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
What to flag
afterEach session logout — will exceed Mendix's 5-session license limit in CIpage.waitForTimeout() / hardcoded sleep — replace with waitForLoadState or a Playwright locator assertion.mx-name-* when a Mendix widget name is availabletoHaveScreenshot requires a baseline PNG in the repopage.waitForLoadState("networkidle") in beforeEach — causes flaky testsWidgetName.spec.js naming conventiondangerouslySetInnerHTML unless input is sanitized with a trusted library (e.g. DOMPurify); flag any unsanitized usage as high severityeval() or new Function() with dynamic input<script> injection must be reviewed for XSS riskhref/src values derived from user input are validated (guard against javascript: and data: URIs)window/document listeners without cleanup — can leak references and be exploitedFull requirements are in docs/requirements/frontend-guidelines.md. Flag only deviations:
<div> or <span> with onClick — replace with <button> or <a>aria-label<img> missing alt attribute (use alt="" for decorative)FloatingFocusManager| Situation | Severity | Comment |
|---|---|---|
| Code/XML changed, no CHANGELOG entry | Medium | pnpm -w changelog — version bumps are a separate PR |
| 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 |
| 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 |
dist/** — build output, never reviewpnpm-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 directoriesdist/tmp/** — intermediate build artifacts**/__snapshots__/** — auto-generated snapshots (flag only if snapshot was deleted without test change)*.mpk — compiled Mendix packagesWhen a PR touches multiple packages, validate each changed package separately:
.github/workflows/**)uses: actions/foo@<sha> # vX.Y${{ secrets.* }} — never hardcoded valuesid-token: write and use OIDC (aws-actions/configure-aws-credentials)pull_request events should guard against fork PRs: check head.repo.full_name or github.repositoryissue_comment) must restrict by author_association to prevent external contributors from triggering privileged workflowstimeout-minutes setconcurrency groups with cancel-in-progress: true will cancel in-flight jobs — use per-job concurrency for interactive workflows.claude/skills/**)name, description) must be present and accuratesrc/** if unsure)Determine the execution context first by checking whether the CI environment variable is set (echo $CI):
CI=true): post the review as a PR comment using gh, following the template below.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.
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 fixedPost 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
#### block — do not bundle multiple issues.[HINT] Download the complete skill directory including SKILL.md and all related files