For comprehensive code quality guidelines, see the code-quality skill.
# Build JavaScript/CSS assets
# IMPORTANT: Always use `npm run build`, not `npx wp-scripts build`.
# npm run build compiles all entry points (index, admin-bar, command-palette).
# npx wp-scripts build only compiles index.js, causing missing asset errors.
npm run buildAdd blank lines before if statements, return statements, and between logical blocks. Code should "breathe" β don't pack statements tightly together.
// Good: Blank lines before if and return.
$simple_history = Simple_History::get_instance();
if ( $this->is_network_query ) {
return $simple_history->get_network_events_table_name();
}
return $simple_history->get_events_table_name();
// Avoid: Everything packed together.
$simple_history = Simple_History::get_instance();
if ( $this->is_network_query ) {
return $simple_history->get_network_events_table_name();
}
return $simple_history->get_events_table_name();Place comments on their own line above the code they explain, not as trailing comments on the same line. This follows WordPress coding standards and improves readability.
// Good: Comment above the code.
// Return early if user is not authorized.
return $result;
// Avoid: Trailing comment.
return $result; // Return because user is not authorizedSimple History supports WordPress 6.3+, so @wordpress/* imports must exist in WP 6.3. The full externals-vs-bundled rules live in src/CLAUDE.md, loaded when working in src/.
- Do NOT upgrade
@wordpress/scriptsbeyond 27.x. Version 28+ makes built assets depend on thereact-jsx-runtimescript handle, which only exists in WP 6.6+ β scripts silently fail to load on WP 6.3β6.5. The leftover npm audit advisories are dev-only and accepted β 27 open as of August 2026, all transitive dev dependencies inpackage-lock.json, none of which ship to users. Note that not all of them come from this pin. See docs/upgrading-wordpress-scripts.md for the full rationale and the two upgrade paths.
Use native HTML elements and CSS before reaching for JavaScript:
<details>/<summary>for expand/collapse instead of JS toggles<dialog>for modals instead of custom JS implementations- CSS
:focus-visiblefor focus states instead of JS focus management - Form validation attributes (
required,pattern,type="email") before JS validation - CSS Grid/Flexbox for layouts instead of JS-based positioning
- Follow WCAG AA: minimum 4.5:1 contrast ratio for text, 3:1 for large text and UI components
- Always provide accessible names for interactive elements (
aria-labelon inputs without visible labels,alton images) - Don't rely on color alone to convey meaning (add text labels, icons, or patterns)
- Use semantic HTML (
<button>for actions,<a>for navigation,<nav>,<main>, etc.)
Every PNG committed to the repo (wordpress.org screenshots and banners, teaser images, OG share cards, icons) must go through pngquant then oxipng before it lands. Never commit a PNG straight out of Playwright or an image editor. The full pipeline and other formats are in the image-optimization skill.
Any prose written for this project (changelogs, commit messages, PR descriptions, blog posts, docs) should read like a person wrote it, not an AI. Avoid stock AI filler phrases and hype words: "seamlessly", "carries through to" / "carry through", "robust", "leverage" (as a verb), "delve into", "elevate", "streamline", "unlock", "empower", "cutting-edge", "game-changer", "boasts", "a testament to", "harness the power of", "in today's ... landscape/world", "when it comes to", "at the end of the day", "it's worth noting that", "furthermore" / "moreover" as transitions.
Prefer a plain, direct sentence over a polished-sounding one. If a phrase sounds like marketing copy or a LinkedIn post, cut it.
Use the simple-history-voice skill for the project voice and rules per text type (changelog, GUI copy, blog posts, newsletters, upsell copy). It runs the draft through the user-level humanizer skill (blader/humanizer, install with npx skills add blader/humanizer --global), which catches the general AI tells.
Use the changelog skill to add entries to readme.txt.
- One long-lived branch:
main. It is always releasable. There is nodevelopbranch (dropped 2026-09-05; the release-to-develop merge-back bought nothing at a one-to-three-week release cadence). - Tiny changes (a typo, changelog wording, a colour value) go straight on
mainas one commit, if they cannot break anything. - Everything else goes on an
issue-NNN-slugbranch and merges intomainwhen the issue is done and looked at. Big or risky work uses a worktree with its own site (scripts/parallel-dev.sh), mergesmaininto itself every few days, and is either merged early behind the experimental features flag or split if it lives longer than a release cycle. - Releases cut
release-X.Y.Zfrommainfor the version bump and changelog heading, tag on that branch, merge back intomainonce. See thereleaseskill. - Hotfixes branch from the tag, tag
X.Y.Z+1, merge intomain. - Run phpstan after making php changes in many files or making a larger change in a single file.
- Run it as
./vendor/bin/phpstan analyse --memory-limit=2G. The default limit crashes a parallel worker part-way through with "PHPStan process crashed because it reached configured PHP memory limit", which reads like a code problem but is not. - Run it with no path argument before committing. Passing a single file skips the check for unmatched
ignoreErrorsentries, and an ignore that no longer matches anything is a hard error here β so a per-file run can pass while the full run fails.