From 209cd1f6ec857f44c661ee9d77587b3a2de0052b Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Tue, 6 Oct 2026 16:02:39 +0200 Subject: [PATCH 1/3] chore(skills): add audit-widget skill with rich text example report Co-Authored-By: Claude Opus 5.5 --- .agents/skills/README.md | 2 + .agents/skills/audit-widget/SKILL.md | 148 +++++++++++ .agents/skills/audit-widget/checklists.md | 237 ++++++++++++++++++ .../examples/rich-text-web.AUDIT.md | 163 ++++++++++++ .../skills/audit-widget/report-template.md | 96 +++++++ 5 files changed, 646 insertions(+) create mode 100644 .agents/skills/audit-widget/SKILL.md create mode 100644 .agents/skills/audit-widget/checklists.md create mode 100644 .agents/skills/audit-widget/examples/rich-text-web.AUDIT.md create mode 100644 .agents/skills/audit-widget/report-template.md diff --git a/.agents/skills/README.md b/.agents/skills/README.md index d53facec3b..9d26dd3261 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -6,6 +6,7 @@ This directory contains all Claude Code skills for the web-widgets repository. ``` .agents/skills/ +├── audit-widget/ # Audit one widget (tests, perf, deps, smells, behaviours, docs) and fix by category ├── code-review/ # PR review skill for Mendix widget conventions ├── debug-widget/ # Debug Mendix pluggable widget runtime issues ├── openspec-apply-change/ # Implement tasks from OpenSpec change @@ -26,6 +27,7 @@ This directory contains all Claude Code skills for the web-widgets repository. Skills are automatically loaded by Claude Code. Users can invoke them via slash commands: +- `/audit-widget ` — Audit one widget into `AUDIT.md`, then `/audit-widget fix` one category at a time - `/code-review` — Review current PR - `/debug-widget` — Debug widget runtime issues - `/openspec-*` or `/opsx:*` — OpenSpec workflow commands diff --git a/.agents/skills/audit-widget/SKILL.md b/.agents/skills/audit-widget/SKILL.md new file mode 100644 index 0000000000..dd19a78bfb --- /dev/null +++ b/.agents/skills/audit-widget/SKILL.md @@ -0,0 +1,148 @@ +--- +name: audit-widget +description: Audit one Mendix pluggable widget across seven categories (unit tests, e2e, performance, dependencies, code smells, missing behaviours, mx-docs drift) into a resumable AUDIT.md, then fix one category at a time, stopping after each for review and commit. +--- + +# Audit Widget + +Two-phase, resumable health check for one widget package. Phase 1 produces a report; Phase 2 fixes it **one category at a time and stops**. + +The report file is the state machine. Everything needed to resume lives in it, so a fresh session can continue an audit it did not start. + +## Invocation + +``` +/audit-widget # Phase 1 — generate/refresh the report +/audit-widget fix # Phase 2 — fix the next pending category, then STOP +/audit-widget fix # Phase 2 — fix a specific category, then STOP +/audit-widget skip # mark a category skipped, change nothing else +/audit-widget status # print the category table, change nothing +``` + +`` is a folder name under `packages/pluggableWidgets/` (e.g. `combobox-web`) or `packages/modules/` (e.g. `data-widgets`). If it is missing or ambiguous, ask — do not guess. + +`` is one of: `unit-tests`, `e2e-tests`, `performance`, `dependencies`, `smells`, `behaviours`, `docs`. + +**Mode resolution:** no mode word → Phase 1. If `AUDIT.md` already exists and Phase 1 is requested again, ask whether to refresh it (re-audit, preserving `fixed`/`skipped` statuses) or to switch to `fix`. + +## Scope discipline + +- Audit **one package**. Read files under `packages///` only. +- Follow findings into `@mendix/widget-plugin-*` / shared packages **only** when the widget's own code cannot explain a finding — and say so explicitly in the finding. Never fix shared-package code as part of a widget audit; raise it as a separate note. +- Dependency analysis uses **only that widget's `package.json`**. Never the root, never another package. +- Never touch `dist/`, generated files (`typings/*.d.ts`), or lockfiles. +- **The one exception is `docs`**: it reads — and in Phase 2 edits — the widget's page in the mx-docs repo. Resolve its location as `$MX_DOCS_PATH`, else `~/Projects/mx-docs`. If neither exists, mark `docs` as `skipped` with the reason in Notes; do not guess another path. Never edit any other mx-docs page than the widget's own (and its `_index.md` only if the page is a section). + +## Phase 1 — Report + +### 1. Orient (cheap, always do this first) + +```bash +cd packages/pluggableWidgets/ +``` + +Read, in this order: + +1. `package.json` — deps, scripts, `widgetName`, `marketplace.minimumMXVersion` +2. `AGENTS.md` if present — the widget's own architecture map, the highest-value context you will get +3. `src/.xml` — the property contract; this is the source of truth for expected behaviour +4. `CHANGELOG.md` — recent behaviour changes, unreleased entries +5. `openspec/specs/**` and `openspec/changes/**` — declared requirements and in-flight work +6. Inventory (do not read contents yet): + ```bash + find src -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.scss" \) | sort + find src -path "*__tests__*" -name "*.spec.*" | sort + ls e2e/*.spec.js + ``` +7. The mx-docs page for the widget — locate it now (lookup recipe in the `DOCS` section of [checklists.md](checklists.md)) and record its path in the report header. Do not read it in full yet. + +### 2. Run the seven checks + +Detection heuristics for each live in [checklists.md](checklists.md). Read it now — it is the substance of this skill. + +| Category | ID prefix | What it answers | +| ------------------ | --------- | ------------------------------------------------------------------- | +| Unit tests | `UT` | Which modules/branches/states have no Jest+RTL coverage? | +| E2E tests | `E2E` | Which user-visible flows have no Playwright spec, or a fragile one? | +| Performance | `PERF` | What causes avoidable renders, work-in-render, or leaks? | +| Dependencies | `DEP` | Patch/minor/major updates available for this widget's own deps | +| Code smells | `SMELL` | Violations of repo conventions, dead code, type escapes | +| Missing behaviours | `BEH` | XML/API/a11y contract the implementation does not honour | +| Public docs | `DOCS` | Where the mx-docs page disagrees with the XML, changelog, or code | + +Parallelising is allowed and encouraged here: dispatch one read-only `Explore` agent per category with the package path plus that category's section of `checklists.md`, then merge results. Keep `dependencies` for yourself — it needs a command run, not a search. + +### 3. Verify before writing + +Every finding must survive this filter, or it does not go in the report: + +- **Evidence**: a `file:line` you actually read, or command output you actually ran. +- **Failure scenario**: concrete inputs/config → wrong or slow result. "Could be cleaner" is not a finding. +- **Not already handled**: check the code path once more for the guard you are claiming is absent. +- **Not a shared-package concern** misattributed to the widget. +- **Not handled by a library**: when the finding depends on how a framework or library behaves (for example, a callback captured by `useEditor(..., [])`), read that library's code in `node_modules` before reporting it. `@tiptap/react` forwards callbacks to the latest options, which made BEH-01 in the example report a false positive. +- **Critical needs proof**: report a finding as Critical only after a failing test or a runtime reproduction. Otherwise report it as High and add _needs repro_. + +Drop speculative findings silently. A short true report beats a long plausible one. + +A real report produced by this skill is in [examples/rich-text-web.AUDIT.md](examples/rich-text-web.AUDIT.md), including findings later marked `fixed`, `stale` and added by tests. + +### 4. Write the report + +Write to `packages///AUDIT.md` using [report-template.md](report-template.md). Then print to the user: the category table, the finding counts per severity, and the 3 findings you would fix first. + +**Stop there.** Phase 1 never edits source, never installs, never commits. + +## Phase 2 — Fix, one category, then stop + +### Order + +`unit-tests` → `e2e-tests` → `performance` → `dependencies` → `smells` → `behaviours` → `docs` + +Tests come first on purpose: they are the safety net for the later categories. `docs` comes last because the earlier categories can change behaviour the docs must then describe. If the user asks for a later category first, do it — but say in one line that the test net is not in place yet. + +### Per-category loop + +1. **Resolve the category.** Explicit arg wins; otherwise the first row in the table whose status is `pending`. If none are pending, report that the audit is complete and stop. +2. **Set status to `in-progress`** in `AUDIT.md`. +3. **Plan.** List the findings you will address, in order, and the ones you are deliberately leaving (with why). If the category has more than ~6 findings, propose a cut line and ask before starting — a 20-file change is not reviewable in one sitting. +4. **Fix.** Work finding by finding. Follow `AGENTS.md`, `docs/requirements/frontend-guidelines.md`, and for e2e work `docs/requirements/e2e-test-guidelines.md`. Prettier and ESLint run automatically on edit — do not invoke them manually. +5. **Verify.** From the package root: + - always: `pnpm run test`. To run a single spec, use `pnpm run test --testPathPatterns --coverage=false`; a bare path argument does not filter, because the script is `jest --projects jest.config.js`. + - `unit-tests`: if a correct assertion fails because the widget is wrong, do not assert the broken behaviour and do not leave an `it.todo`. Add the bug to the report as a new finding (next free ID in the right category, marked _found by tests_) and leave the test out until the fix PR. + - `e2e-tests`: `pnpm run release` then `pnpm run e2e --no-setup-project`. If Docker or the test project is unavailable, say so plainly and leave the specs as written-but-unverified — do not claim a pass you did not see. + - `dependencies`: `pnpm run test` **and** `pnpm turbo build` + - `performance`, `behaviours`: `pnpm run test`; add or update a test that would have caught the issue + - `docs`: no widget command. Instead, before editing, confirm the mx-docs working tree is clean and not on its default branch (`development`); if it is, create a local branch `docs/-sync` — never edit on the default branch, never push. Edit following mx-docs `CLAUDE.md` and its style guide (it takes precedence over this repo's conventions inside mx-docs). Afterwards run `npx markdownlint-cli2 ` from the mx-docs root if available, and re-read every changed `{#anchor}` / URL with a grep for inbound references. +6. **Changelog.** If widget behaviour changed, add an entry under `## [Unreleased]` in the package `CHANGELOG.md`. User-facing wording only — no implementation detail. Do **not** bump the version. Pure test-only or dependency-only changes with no user-visible effect get no entry. `docs` never adds a widget changelog entry. +7. **Update `AUDIT.md`**: set each addressed finding's status to `fixed` (or `skipped` with a reason), set the category status to `done`, and append a line to the Log section: ` — — N fixed, M skipped`. +8. **HALT.** + +### The halt is the point + +After one category, you are finished for this invocation. That means: + +- Do **not** start the next category, even if it looks trivial or related. +- Do **not** `git add`, `git commit`, `git push`, or open a PR. The user commits. +- Do **not** ask "shall I continue?" — the user will invoke the skill again when ready. + +Print exactly this shape and then end the turn: + +``` +✅ — N fixed, M skipped + + +Verification: → +Next pending: (K findings) + +Suggested commit: +(): +``` + +Commit `type` by category: `unit-tests`/`e2e-tests` → `test`, `performance` → `perf`, `dependencies` → `chore`, `smells` → `refactor`, `behaviours` → `fix` or `feat`. Scope is the folder name (`combobox-web`). + +For `docs` the commit lives in the **mx-docs** repo, not this one: print the mx-docs branch name and a plain subject line (mx-docs does not use conventional commits), e.g. `Combo box: document , sync Properties Pane with v2.4.0`. + +## Resuming cold + +If `AUDIT.md` exists and you have no memory of writing it: trust it. Read it, read the widget's `AGENTS.md`, then act on the requested mode. Re-verify a finding's `file:line` before fixing it — the file may have moved since the report was written. If a finding no longer reproduces, mark it `stale` with a one-line note rather than inventing a fix. diff --git a/.agents/skills/audit-widget/checklists.md b/.agents/skills/audit-widget/checklists.md new file mode 100644 index 0000000000..282ca0c21c --- /dev/null +++ b/.agents/skills/audit-widget/checklists.md @@ -0,0 +1,237 @@ +# Detection heuristics per category + +One section per audit category. Each finding you report must trace back to a concrete signal here (or a defensible variation of one) plus `file:line` evidence. + +Severity scale used throughout: + +- **Critical** — users hit broken/incorrect behaviour, or data loss +- **High** — a real defect or a gap that lets defects ship undetected +- **Medium** — degraded UX/perf, or a convention violation with maintenance cost +- **Low** — cleanup, nice-to-have coverage + +--- + +## UT — Missing unit tests + +Target: `src/**/__tests__/*.spec.{ts,tsx}` run by `pnpm run test` (Jest + RTL). + +**Build the coverage map first.** For every file under `src/` that exports something, note whether any spec imports it. Optionally run `pnpm run test --coverage` for real numbers; if the toolchain rejects the flag, fall back to import analysis and say the numbers are structural, not measured. + +Then look for these specific gaps: + +| Signal | Severity | +| --------------------------------------------------------------------------------------------------------------------- | -------- | +| Entry component (`src/.tsx`) has no spec at all | High | +| A `src/helpers/**` class or pure function has no spec | High | +| A custom hook in `src/hooks/**` has no spec | High | +| `.editorConfig.ts` `getProperties` / `check` / `getPreview` untested | Medium | +| Branch untested: each mutually exclusive XML enum value (e.g. selection mode, datasource type) that changes rendering | High | +| State untested: loading, unavailable/placeholder, empty/no-results, read-only, validation-message, disabled | High | +| `ActionValue` prop with no test asserting `canExecute === false` blocks `execute()` | High | +| `EditableValue.setValue` path with no test asserting the value written | High | +| Keyboard handler with no test for its keys (Arrow/Enter/Space/Escape/Tab/Home/End) | High | +| ARIA attributes / roles rendered but never asserted | Medium | +| Snapshot exists but no behavioural assertions accompany it | Medium | +| Cleanup untested: listener/timer/observer added in an effect, no unmount assertion | Medium | +| Spec exists but only covers the happy path of a multi-branch function | Medium | +| Test uses real timers/`setTimeout` sleeps instead of fake timers | Low | + +Prefer `@mendix/widget-plugin-test-utils` builders (`dynamicValue`, `listValue`, `actionValue`, …) when proposing new tests — check how sibling specs in the package do it and match. + +--- + +## E2E — Missing e2e flows + +Target: `e2e/*.spec.js` run via `@mendix/run-e2e`. Rules: `docs/requirements/e2e-test-guidelines.md`. + +**Enumerate the surface first.** Widget features from the XML + the pages in the test project: + +```bash +bash automation/mxcli/mx-testproject.sh inspect # modules, pages, entities, .mx-name-* labels +``` + +If `automation/tools/mxcli` is missing, note it and fall back to reading `e2e/*.spec.js` selectors to infer the page inventory. + +Coverage gaps: + +| Signal | Severity | +| ---------------------------------------------------------------------------------- | -------- | +| A test-project page exercising this widget has no spec referencing it | High | +| A primary interaction (select, filter, sort, open/close, upload, drag) has no spec | High | +| Full keyboard navigation flow untested end-to-end | High | +| No `toHaveScreenshot` coverage for a visually-driven widget | Medium | +| Dark-mode / design-property variants rendered but never screenshotted | Low | +| Error/validation path never driven through the UI | Medium | +| Feature added in a recent `CHANGELOG.md` entry with no matching spec | High | + +Fragility gaps (report these as E2E findings too — a flaky spec is worse than a missing one): + +| Signal | Severity | +| ---------------------------------------------------------------------- | -------- | +| Import from `@playwright/test` instead of `@mendix/run-e2e/fixtures` | High | +| `page.waitForTimeout(...)` | High | +| `waitForLoadState("networkidle")` | High | +| `waitForMendixApp()` called manually right after `page.goto()` | Low | +| `.nth(N)` on an ambiguous selector | Medium | +| Bare `text=` / `page.click("text=…")` locator | Medium | +| `allTextContents()` / `$$eval` / `evaluate` used to build an assertion | Medium | +| Text-content correctness asserted in e2e rather than a unit test | Low | +| `press("Escape")` on a popup page | Medium | +| Manual login / manual logout in `afterEach` | Low | + +--- + +## PERF — Performance issues + +Read the render path top-down from the entry component. Concrete, not vibes: each finding names what re-renders or what work repeats. + +| Signal | Severity | +| ------------------------------------------------------------------------------------------------------------------------- | -------- | +| Object/array/function literal passed as a prop into a `memo`'d child (kills the memo) | High | +| `useEffect`/`useMemo` dependency array containing a value recreated every render | High | +| Class/selector/provider instance constructed in the render body instead of `useMemo`/`useRef`/state initialiser | High | +| Expensive work in render: sort, filter, `match-sorter`, regex build, date formatting over a full list | High | +| Deriving state from props via `useState` + `useEffect` instead of computing during render | Medium | +| MobX-observable state read in a component not wrapped in `observer` (extra renders or missed updates) | High | +| Missing `useCallback` on a handler passed to a large list's items | Medium | +| List of unbounded length rendered without virtualisation or a cap | Medium | +| `key` derived from index on a reorderable/filterable list | Medium | +| Layout read (`getBoundingClientRect`, `offsetWidth`) in an effect that also writes style — forced sync reflow | Medium | +| `ResizeObserver`/`scroll`/`resize` listener without throttle, or without cleanup | High | +| Effect that calls `setValue`/`execute` on every render tick | Critical | +| Timer/interval/listener/observer with no cleanup in the effect return | High | +| Non-tree-shakable import (`import _ from "lodash"`, deep barrel re-export) — repo requires tree-shakable imports | Medium | +| Large asset or icon inlined per render instead of a stable module-level constant | Low | +| SCSS: deeply nested selectors, or animation on a non-composited property (`width`/`top` instead of `transform`/`opacity`) | Low | + +For each PERF finding, state the trigger: "on every keystroke", "on every parent render", "once per row per render". + +--- + +## DEP — Dependency updates + +**Only this widget's `package.json`.** Run from the package root: + +```bash +pnpm outdated +``` + +Classify each result against the **range declared in this widget's `package.json`**: + +- **patch** — `x.y.Z`; safe, batch them together +- **minor** — `x.Y.z`; read the changelog for behaviour changes +- **major** — `X.y.z`; breaking; report only, propose separately, never bundle with other work + +Rules and hard constraints: + +- Skip every `workspace:*` / `workspace:^*` dependency — those move with the monorepo, not with this widget. +- `react` / `react-dom` are supplied by the Mendix client and pinned repo-wide to `>=18.0.0 <19.0.0`. Never propose a React 19 bump, and reject any dep whose new version requires React 19. +- `@mendix/pluggable-widgets-tools` is `*` — do not pin it. +- Check the new version still supports the widget's `marketplace.minimumMXVersion` browser baseline; flag ESM-only majors, since the widget is bundled by Rollup for the Mendix client. +- Renovate manages the repo (`.github/renovate.json`). If a bump appears to already be in an open Renovate PR, note it rather than duplicating the work. +- Never edit lockfiles by hand. Bumps go through `pnpm up @` inside the package. + +Report as a table: package, current, wanted, latest, kind (patch/minor/major), risk note. + +--- + +## SMELL — Code smells + +Repo conventions come from `AGENTS.md` and `docs/requirements/frontend-guidelines.md`. + +| Signal | Severity | +| ---------------------------------------------------------------------------------------------- | -------- | +| `ActionValue.execute()` called without a `canExecute` check | High | +| Direct DOM manipulation (`document.querySelector`, manual `classList`) instead of React state | High | +| `any`, `as unknown as`, or `!` non-null assertion covering a real nullable case | Medium | +| `@ts-ignore` / `@ts-expect-error` without an explanatory comment | Medium | +| Inline `style={{…}}` for static design instead of an SCSS class | Medium | +| `!important`, or a rule overriding a core Atlas class | Medium | +| Custom class not prefixed with the widget name (`.widget--…`) | Low | +| XML property key not matching the TS prop name, or not lowerCamelCase | High | +| Prop declared in XML but never read in the component | High | +| `console.log` / `debugger` left in `src/` | Medium | +| Dead code: unexported unused helper, commented-out block, unreachable branch | Low | +| Duplicated logic across components that belongs in `src/helpers/` or a shared plugin package | Medium | +| Component file well over the package's norm (compare siblings) doing three unrelated jobs | Medium | +| Hardcoded user-facing string where the widget elsewhere uses XML/translation properties | Medium | +| `CHANGELOG.md` missing an `## [Unreleased]` entry for behaviour shipped since the last version | Medium | +| Changelog entry describing implementation rather than user-visible behaviour | Low | +| Missing `README.md` feature docs for a shipped XML property | Low | + +--- + +## BEH — Missing behaviours + +The XML is the contract. Walk it property by property and confirm the implementation honours each one. + +| Signal | Severity | +| ----------------------------------------------------------------------------------------------------------------------- | -------- | +| XML property has no effect in the rendered output | High | +| System property declared (Visibility, Tab index, Name) but `tabIndex`/`class`/`style` not forwarded to the root element | Medium | +| No loading state while `ListValue.status === "loading"` / `DynamicValue.status !== "available"` | High | +| No empty state when the datasource returns zero items | Medium | +| `EditableValue.readOnly` not honoured — control stays interactive | High | +| `EditableValue.validation` present but no validation message rendered | High | +| `editorPreview.tsx` visibly diverges from runtime rendering (missing a whole feature) | Medium | +| `editorConfig.ts` does not hide properties irrelevant to the selected mode | Low | +| Offline-capable widget doing something that requires the network | High | +| No cleanup on unmount for something that outlives the component | High | +| Requirement in `openspec/specs/**` with no implementation | High | +| `CHANGELOG.md` documents behaviour the code does not do (or no longer does) | High | + +Accessibility (WCAG 2.2 AA — `docs/requirements/frontend-guidelines.md`): + +| Signal | Severity | +| --------------------------------------------------------------------------------------------------------------------------------- | -------- | +| Interactive element built from `div`/`span` where a native `button`/`input`/`dialog` fits | High | +| Keyboard path missing: Arrows for list/menu, Enter/Space to activate, Escape to dismiss, Tab order | High | +| List/menu without roving tabindex (active `0`, rest `-1`) | Medium | +| Floating element (menu/tooltip/popover) not using Floating UI's `useRole`/`useDismiss`/`useListNavigation`/`FloatingFocusManager` | Medium | +| Focus not restored to the trigger after a menu/dialog closes | High | +| Icon-only control with no accessible name | High | +| ARIA added where semantic HTML already conveys it (over-engineering — report as cleanup) | Low | +| Contrast below 4.5:1 (normal text) / 3:1 (large text, GUI elements) in the widget's SCSS | Medium | +| State change with no screen-reader announcement (`aria-live`, `aria-expanded`, `aria-selected`) | Medium | + +--- + +## DOCS — Outdated public docs (mx-docs) + +Target: the widget's page in the mx-docs repo (`$MX_DOCS_PATH`, else `~/Projects/mx-docs`). The widget's XML, `package.json`, and `CHANGELOG.md` are the source of truth; the doc page is what drifts. + +**Locate the page first.** Widget pages live under `content/en/docs/marketplace/platform-supported-content/widgets/`, module pages under `.../modules/` (sometimes a folder with `_index.md` plus one page per widget, e.g. `modules/data-widgets/`). Resolve by the marketplace link, which every page carries in its intro: + +```bash +cd "${MX_DOCS_PATH:-$HOME/Projects/mx-docs}" +appNumber=$(node -p "require('/package.json').marketplace.appNumber") +grep -rl "link/component/$appNumber" content/en/docs/marketplace/platform-supported-content +``` + +Several pages can link the same component (cross-references). Pick the one whose `url:` front matter / filename matches `marketplace.appName` and that links it in the `## Introduction` paragraph. For widgets inside a module (`packages/modules/`), match the page by widget name inside the module folder. If no page is found, that is itself a finding (below) — do not invent one. + +**Then compare.** Walk the XML property groups against the page's `## Properties Pane` section — mx-docs mirrors Studio Pro's tabs as `### Tab {#anchor}` headings (General, Events, Accessibility, Advanced, Common…), with one bold item or sub-heading per property using its XML ``. + +| Signal | Severity | +| ----------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| No mx-docs page exists for a released widget | High | +| XML property (non-system, visible in Studio Pro) not documented on the page | High | +| Page documents a property that no longer exists in the XML, or under an old caption | High | +| Enum value added/removed in the XML but the page's list of options is unchanged | High | +| Feature in a released `CHANGELOG.md` version (not `[Unreleased]`) with no mention on the page | High | +| Page describes behaviour the code does not do (default value, condition, what an option does) — cite the code line | High | +| Property documented under the wrong tab/group compared to the XML `propertyGroup` | Medium | +| Default value stated on the page differs from the XML `defaultValue` | Medium | +| Minimum Mendix version stated on the page lower/higher than `marketplace.minimumMXVersion` | Medium | +| "Limitations" / "Known issues" section lists something a released version fixed | Medium | +| Design properties listed on the page don't match `src/themesource/**/design-properties.json` (modules only — skip for standalone widgets) | Low | +| Screenshot/GIF shows a visibly outdated UI (old caption, removed control) — report only, never regenerate images | Low | + +Evidence for each finding is **two** locations: the doc `file:line` and the widget `file:line` (XML / changelog / source) it disagrees with. + +Rules: + +- Only released behaviour belongs in mx-docs. `[Unreleased]` changelog entries are not findings — the docs ship with the release. +- `editorConfig.ts`-hidden properties are still documented if a user can reach them in some mode; check the hide condition before calling one "internal". +- Wording, tone, and grammar are not findings here. Accuracy only — leave style to mx-docs' own `docs-proofread`/`docs-review` skills. +- Phase 2 edits must follow mx-docs `CLAUDE.md`: smallest edit that fixes the accuracy problem; ask before deleting more than a sentence or renaming a `{#anchor}`/`url:` (grep for inbound references first); never add claims not sourced from the widget's XML, changelog, or code. diff --git a/.agents/skills/audit-widget/examples/rich-text-web.AUDIT.md b/.agents/skills/audit-widget/examples/rich-text-web.AUDIT.md new file mode 100644 index 0000000000..f886dbbad2 --- /dev/null +++ b/.agents/skills/audit-widget/examples/rich-text-web.AUDIT.md @@ -0,0 +1,163 @@ +# Audit — rich-text-web + +- **Package:** `@mendix/rich-text-web` v5.1.0 +- **Audited:** 2026-10-05 +- **Min Mendix:** 9.24.0 +- **Coverage basis:** structural (import analysis); UT findings fixed in mendix/web-widgets#2453 +- **mx-docs page:** `content/en/docs/marketplace/platform-supported-content/widgets/richtext/_index.md` + +## Categories + +| Category | Status | Findings | Critical | High | Medium | Low | +| -------------------------------------- | ----------- | -------- | -------- | ---- | ------ | --- | +| Unit tests (`unit-tests`, UT) | done | 12 | 0 | 10 | 2 | 0 | +| E2E tests (`e2e-tests`, E2E) | pending | 12 | 0 | 8 | 3 | 1 | +| Performance (`performance`, PERF) | pending | 8 | 0 | 2 | 3 | 3 | +| Dependencies (`dependencies`, DEP) | pending | 4 | 0 | 0 | 2 | 2 | +| Code smells (`smells`, SMELL) | pending | 11 | 0 | 0 | 7 | 4 | +| Missing behaviours (`behaviours`, BEH) | in-progress | 18 | 1 | 10 | 7 | 0 | +| Public docs (`docs`, DOCS) | pending | 13 | 0 | 9 | 3 | 1 | + +Status values: `pending` · `in-progress` · `done` · `skipped` + +## Summary + +The Tiptap migration (5.0.0) left the runtime contract with Mendix under-tested and several behaviours silently broken. The worst confirmed issue is BEH-17: with `styleDataFormat = "class"` (strict CSP), color, font family and font size are lost when saved content is loaded again. Four XML properties (`spellCheck`, `linkValidation`, `defaultFontFamily`, `defaultFontSize`) do nothing (BEH-04..06), and read-only changes after mount never apply (BEH-02). Toolbar keyboard accessibility is incomplete. Unit-test coverage is done (mendix/web-widgets#2453); those tests confirmed BEH-02, BEH-12 and BEH-15 and found BEH-17..19. BEH-01 was a false positive (see Notes). Fix next: the small, independent bugs (BEH-17, 02, 03, 11, 18, 15, 19), each with a regression test. BEH-04..06 need a product decision (restore or remove), which also blocks DOCS-01. + +--- + +## Unit tests (UT) + +| ID | Severity | Finding | Evidence | Proposed fix | Status | +| ----- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| UT-01 | High | No test for `onChange`/`onFocus`/`onBlur`/`onLoad`, either that they execute or that `canExecute === false` blocks them. Neither `onChangeType` branch is tested. | `src/components/Editor.tsx:373-384`; `src/components/EditorWrapper.tsx:68-70` | `RichText.actions.spec.tsx` with `actionValue()`. Assert onLoad on mount, focus/blur execution, onChange on blur only for `onLeave`, on typing only for `onDataChange`, and no execute when `actionValue(false)`. Include a rerender with new ActionValues (BEH-01). | fixed | +| UT-02 | High | No positive test for the `setValue` write path. The only assertions are `not.toHaveBeenCalled()`. `"

"`→`""` normalisation is untested. | `src/components/EditorWrapper.tsx:55-81`; `src/__tests__/RichText.spec.tsx:123,132` | Type into the editor and assert `setValue` is called with the HTML. Clear the editor and assert `setValue("")`. Rerender with an updated attribute, edit back to the original value, and assert it is written (regression for BEH-01). | fixed | +| UT-03 | High | No test for the loading, unavailable and validation states. | `src/RichText.tsx:12-13,25`; `src/components/EditorWrapper.tsx:113` | `EditableValueBuilder().isLoading()` → `.mx-progress` and no `.ProseMirror`. `.isUnavailable()` → no editor. `.withValidation("Required")` → alert with that text. | fixed | +| UT-04 | High | `readOnlyStyle` `text`/`readPanel` and a read-only change after mount are untested (`bordered` is covered by a snapshot only). | `src/RichText.tsx:19`; `src/components/EditorWrapper.tsx:147`; `src/components/Editor.tsx:368` | One test per style for the class and toolbar visibility. Rerender from editable to read-only and assert `contenteditable="false"` (regression for BEH-02). | fixed | +| UT-05 | High | `toolbarLocation` `hide`/`auto` untested; `top`/`bottom` only appear in snapshots. | `src/components/Editor.tsx:422,433`; `src/components/EditorWrapper.tsx:108` | `it.each(["auto","top","bottom","hide"])`: assert the `toolbar-` class, and that no toolbar renders only for `hide`. | fixed | +| UT-06 | High | `styleDataFormat="class"` branch of the text-mark extensions is untested, even though it changes the persisted HTML. | `src/extensions/TextColorClass.ts:42-58`; same in FontFamilyClass/FontSize/TextAlignClass/TextHighlightClass | `extensions/__tests__/classFormat.spec.ts`: round-trip `setContent(getHTML())` keeps the `data-*` attribute and class with no inline style. Unsafe values are dropped. | fixed | +| UT-07 | High | No keyboard handler has a test: Alt+F10/F11, Escape, fullscreen Escape, split button arrows, config dropdown Enter/Escape. | `src/extensions/KeyboardNavigation.ts:32-103`; `src/extensions/Fullscreen.ts:68-79`; `ToolbarSplitButton.tsx:120-146` | Render `preset="full"` and assert the focus target after each key. | fixed | +| UT-08 | High | The security-relevant embed allowlist (`parseEmbedCode`, `videoUrlPattern`, `GenericEmbed` paste vetting) has no spec. | `src/utils/embedCodeParser.ts:34-129`; `src/extensions/GenericEmbed.ts:9-14` | Table-driven spec with `javascript:`, `data:`, `ftp:`, `youtube.com.evil.io`, malformed and valid inputs. Pasting `