This file is for AI coding agents working in webentor-stack.
webentor-stack is the source of truth for shared Webentor packages and docs.
The monorepo exists to keep versioning and integration changes coordinated.
packages/webentor-core: shared PHP + JS runtime package.packages/webentor-configs: shared ESLint/Stylelint/Prettier/static config package.packages/webentor-setup: shared setup runtime + setup CLI.packages/webentor-codemods: reusable ast-grep codemods for migrating consumer theme code acrosswebentor-coreupdates (npm-published; run viapnpm dlx).packages/webentor-starter: pure project skeleton (WordPress/theme only; no setup scaffolding).docs: VitePress documentation project.test-site/: spin-up Bedrock E2E harness mirroring the starter, withwebentor-coresymlinked (see Testing Suite (E2E)).tests/: Playwright E2E specs + fixtures that run against the provisionedtest-site.
webentor-demois not part of this monorepo; it is a separate integration-gate repository.packages/webentor-setupandpackages/webentor-starterare mirrored to standalone repositories.- In consumer projects,
scripts/setup-core/is added viagit subtree addfrom a taggedwebentor-setuprelease. - All other scaffolding under
scripts/and.webikon/is generated bywebentor-setup init. - Project-specific setup customizations must live in:
scripts/.env.setupscripts/hooks/scripts/project-specific/
Allowed direction:
webentor-starter->webentor-core,webentor-configs,webentor-setup(as runtime copy/subtree output)webentor-demo(external) -> released outputs from monorepo packages
Avoid reverse dependencies from shared packages back into starter/demo.
packages/webentor-setup defines setup contract.
The init command generates all scaffolding (.env.setup, setup.sh, hooks,
project-specific, optionally ts-up.sh, and .webikon/project.json).
Without flags it prompts interactively; with flags the corresponding prompts
are skipped (useful for CI/AI). The starter contains no setup files — it is a
pure project skeleton.
.webikon/project.json (schema v2) declares four required fields —
schema_version, slug, stack, theme_path — plus the optional
setup_cli_version, and nothing else. It carries exactly one version, that one,
and no feature toggles; every other such fact is read from the artifact that owns
it. The maintenance reporter (≥ 2.5.0) derives what is visible from a deployed
site: theme Composer/pnpm locks for webentor-core and webentor-configs, root
composer.json version for the starter release, WP_CONTENT_DIR for layout.
stack is a closed enum (webentor-v2, webentor-v2-hybrid, webentor-v1,
sage, classic); layout is never encoded in it. init is merge-aware — a
hand-set stack survives re-runs, --stack <value> overrides it — and it
deletes the retired v1 file at .webentor/project.json.
setup_cli_version is the one declared rather than derived version, and it
exists for one reason: its artifact is scripts/setup-core/composer.json, and
scripts/ is deploy-excluded, so a production host cannot read it — the JSON
carries the value across the deploy boundary. The inclusion test is not just
"must not be derivable": derivable in the repo is not derivable from a
deployed site, and the reporter only ever sees the latter. init writes the
field by mirroring that composer.json and never substitutes the running CLI's own
version, so a project with no scripts/setup-core/ — a hybrid, a plain Sage or
classic project, and the starter package itself — gets no field. Being declared,
it is also the one field that can rot: webentor-setup doctor compares it against
the artifact and exits 1 on mismatch, and
webikon:update-webentor-packages-in-project BLOCKs, because a git subtree pull
of setup-core is a two-part change. Readers prefer the artifact when readable and
fall back to the declaration otherwise — so fix the declaration to match the
artifact, never the reverse.
The SETUP_* toggles are not in the file and were dropped from the reporter and
dashboard entirely: they also live under scripts/, they were always all-false in
production, and nothing acted on them. They stay a checkout-only fact read from
scripts/.env.setup. Do not re-add them anywhere.
If setup runtime behavior changes, update all of these together:
- runtime scripts (
common.sh, helpers, platform scripts) - CLI behavior (
src/webentor-setup.php) — includinginitscaffolding generators - docs/README contract text
- upgrade manifests in
upgrades/<version>/manifest.jsonif migration behavior changes
In consumer projects:
scripts/setup-core/is not in the starter — it is added viagit subtree addfrom a taggedwebentor-setuprelease.scripts/setup.shand all other scaffolding underscripts/are generated bywebentor-setup init.- Do not place project business logic in
setup-core. - Do not put generated scaffolding files back into
packages/webentor-starter.
When packages/webentor-setup changes, keep consumer runtime in sync intentionally.
test-site/ is a Bedrock install used to test blocks/runtime against a real WordPress site.
pnpm test:setup provisions it; pnpm test:e2e runs the Playwright specs in tests/;
pnpm test:teardown removes it. The site serves at http://webentor-test.test (admin admin/admin).
Linkage (this is the key mental model):
webentor-coreis symlinked into the test-site for BOTH PHP (Composer path repo) and JS (pnpmlink:). Edits topackages/webentor-coreare picked up directly.- The theme is a copy, not a symlink —
test-env-setup.shrsyncs it frompackages/webentor-starter/web/app/themes/webentor-theme-v2. Edit the starter source and re-run setup to re-sync, or edit the test-site copy directly while iterating (it is the source of truth that matters for a committed change).
Reflecting webentor-core changes in the running site:
- PHP (Providers, hooks, block render, Blade views): live via the symlink — run
wp acorn optimize:clearfromtest-site/to flush Acorn's cached views/config/services. - JS / CSS (
webentor-coreships TS/CSS source, nodist— it's bundled by Vite). There are TWO separate bundles, and which one to rebuild depends on what changed:- Block editor JS (block
editcomponents, inspector controls, block toolbars, block registration) is compiled intowebentor-core's OWN bundle (public/build, enqueued aswebentor-core-editor-js). Rebuild withpnpm buildinpackages/webentor-core— the theme build does NOT recompile this. (block.jsonchanges likeapiVersion/supportsare read server-side at registration, so they apply via the symlink without any JS rebuild.) - Theme-side JS/CSS (theme
editor.ts,app.ts, app styles, anything importing@webikon/webentor-core/*helpers) is bundled by the test-site theme's Vite — rebuild withpnpm build(orpnpm devfor HMR) in the test-site theme. - When in doubt after editing
webentor-core, rebuild both.
- Block editor JS (block
Operational gotchas:
- Run on a specific PHP version (e.g. 8.4 / 8.5):
herd isolate <ver> --site webentor-test(the site is linked aswebentor-testthough the directory istest-site, so--siteis required). CLI provisioning runscomposer/wp, which resolvephpfromPATH— to provision on a non-global PHP, prepend aphp→target-binary shim toPATH. - Database: set
DB_HOSTintest-site/.envto the server over TCP (127.0.0.1) when its socket differs from the client default (e.g. DBngin listens on/tmp/mysql_3306.sock). - WP core version bumps: setup runs
wp core update-db; without it the editor (and every editor-dependent test) is stuck on the "Database Update Required" screen. - E2E auth requires
wps-hide-loginto be inactive (it 404swp-login.php); setup/tests expect the standard login path. - WP 7.0 iframed editor: the editor canvas is in an iframe, so block-editor specs must use
frameLocator('[name="editor-canvas"]')for canvas selectors — top-frame selectors like.block-editor-writing-flowtime out. - ACF Pro key (needed for
pnpm test:setup): 1Password item "ACF PRO licence", Clients vault, in thenotesPlainfield. Goes intotest-site/.envasPLUGIN_ACF_KEY; never commit.env.
Block-console checker: node scripts/check-blocks-console.mjs logs into the test-site, inserts
every top-level webentor/* block into a published page, and reports editor + frontend console
messages (block validation errors, deprecation notices). Use it to catch block-editor regressions.
Default order for coordinated releases:
webentor-coreand/orwebentor-configswebentor-setup(including any upgrade recipe)webentor-starterwebentor-demobump/validation (external repo)
Shared release is not complete until demo CI is green.
- Use independent semver per package; do not force shared version numbers across the monorepo.
- On every release request, update
docs/src/compatibility-matrix.mdwith the tested-together version set.
Maintainers choose version bumps and write changelog entries manually — the
committed version and changelog files are the source of truth. Everything
after the merge is automated: publishing, tagging, and mirror splits react to
what is committed on main.
- Decide the semver bump for each affected package.
- Run
pnpm release:prep <pkg>=<x.y.z> [...](pkg:core,configs,setup,codemods,starter,theme). It stamps every mirrored version source (per-package manifests + CHANGELOG entries) and inserts the compatibility-matrix row. Bump packages that release together in ONE invocation so the matrix gains a single row. On astarterbump it also refreshespackages/webentor-starter/composer.lock— Composer hashesversioninto the lock'scontent-hash, so stamping the manifest alone leaves the lock stale and CI'scomposer validateexits 2. That step needscomposeron PATH; without it the script warns and you must runcomposer update --lock --no-installin the starter yourself. - Replace the scaffolded TODO changelog line(s) with curated entries.
- Run
pnpm check:versions(CI enforces the same check on every PR/push). - Commit and push the release preparation through the normal review flow.
A starter release MUST bump packages/webentor-starter/composer.json
version in the release commit. That value is load-bearing, not cosmetic:
consumer projects inherit the manifest, and the maintenance reporter derives a
site's starter release from its root composer.json version. Ship a starter
release without the bump and every project it lands on reports a stale release
until someone notices by hand. A starter release needs no metadata-file update —
the only version .webikon/project.json declares is setup_cli_version, which
tracks scripts/setup-core/composer.json, not the starter release. (A
webentor-setup release does move it, but only in consumer projects that pull the
setup-core subtree; the starter package ships no scripts/setup-core, so its own
file has no such field.)
release.yml runs automatically:
-
scripts/check-versions.mjs— aborts if any mirrored version source drifted. -
scripts/publish-npm.mjs— publishes any npm package (core,configs,codemods) whose committed version is not on the registry yet. -
scripts/release-tags.sh— for every released package (core,configs,codemods,setup,starter; the theme ships inside the starter and has no tag of its own) ensures three things exist for the committed version:- the namespaced tag (
core-v*,configs-v*,codemods-v*,setup-v*,starter-v*) if it does not exist yet; - a GitHub Release on that tag, titled with the package's canonical
manifest name (
@webikon/webentor-core@0.15.7,webikon/webentor-setup@1.1.0) and carrying that version's CHANGELOG section as its notes; - a split-workflow dispatch, for mirrored packages only:
core-v*->split-webentor-core.yml(also pings Packagist)setup-v*->split-webentor-setup.ymlstarter-v*->split-webentor-starter.yml
The tag and Release checks are independent: a version whose tag exists but whose Release is missing gets the Release backfilled on the next run. Split dispatch only fires for a freshly created tag, so mirrors are never re-pushed. Run with
DRY_RUN=trueto preview. - the namespaced tag (
The Release title deliberately reproduces the @webikon/webentor-*@* scheme the
changesets action used before it was removed, so the Releases page reads
continuously across the migration even though the underlying tags use
<ns>-v<version>.
No manual tag pushes are needed for a normal release. To re-run a mirror split
manually, dispatch the split workflow with the tag as input, or push the tag
by hand (git tag core-v0.9.14 && git push origin core-v0.9.14).
When asked to release a package:
- Identify the affected package(s) and choose the correct bump level.
- Run
pnpm release:prepwith all packages releasing together. - Replace the scaffolded TODO changelog line(s) with curated entries.
- Verify with
pnpm check:versionsandpnpm test-release. - Commit and push through the normal review flow. Publishing and tagging
happen automatically after the merge to
main.
Reference map for locating version sources per package.
webentor-core(beta,0.x):packages/webentor-core/package.json->"version"(manual)packages/webentor-core/composer.json->"version"(manual; keep in sync withpackage.json)packages/webentor-core/CHANGELOG.md-> add new entry at top (manual)- npm publish via the existing release workflow; Composer/Packagist via split-mirror + Packagist API ping
webentor-configs(stable,1.x):packages/webentor-configs/package.json->"version"(manual)packages/webentor-configs/CHANGELOG.md-> add new entry at top (manual)- npm publish via the existing release workflow
webentor-setup(stable,1.x):packages/webentor-setup/CHANGELOG.md-> add new entry at top (manual)- Runtime is mirrored via split workflow (no npm publish)
packages/webentor-setup/composer.json->"version"(manual)
webentor-codemods(beta,0.x):packages/webentor-codemods/package.json->"version"(manual)packages/webentor-codemods/CHANGELOG.md-> add new entry at top (manual)- npm publish via the existing release workflow (no Composer mirror)
webentor-starter(stable,2.x):packages/webentor-starter/composer.json->"version"(manual; the reporter derives a consumer site's starter release from this value, so it must be bumped in the release commit)packages/webentor-starter/CHANGELOG.md-> add new entry at top (manual)- Runtime is mirrored via split workflow (no npm publish)
webentor-theme-v2(stable,2.x, inside starter):packages/webentor-starter/web/app/themes/webentor-theme-v2/package.json->"version"(manual)packages/webentor-starter/web/app/themes/webentor-theme-v2/composer.json->"version"(manual)packages/webentor-starter/web/app/themes/webentor-theme-v2/style.css->Version:header (manual)packages/webentor-starter/web/app/themes/webentor-theme-v2/CHANGELOG.md-> add new entry at top (manual)- Keep all theme version sources synchronized in the same bump
pnpm release:prep edits all of these in one command; pnpm check:versions
(and the versions CI job) fails on any mismatch, including the
compatibility-matrix top row. The same check guards the starter's
.webikon/project.json shape (schema v2, the four required keys plus the
optional setup_cli_version, no unknown keys, known stack, resolvable
theme_path, and setup_cli_version rejected outright since the starter ships no
scripts/setup-core) and the starter root composer.json name/version — the
metadata file itself holds no version baseline this repo could compare against.
Mirror targets and workflows:
packages/webentor-core->.github/workflows/split-webentor-core.yml/scripts/split-webentor-core.sh(tag:core-v*)packages/webentor-setup->.github/workflows/split-webentor-setup.yml/scripts/split-webentor-setup.sh(tag:setup-v*)packages/webentor-starter->.github/workflows/split-webentor-starter.yml/scripts/split-webentor-starter.sh(tag:starter-v*)
The split-webentor-core.yml workflow also pings Packagist after mirroring to update the Composer package.
Secrets required: PACKAGIST_USERNAME, PACKAGIST_API_TOKEN.
Do not introduce additional split mirrors unless explicitly requested.
- This is a PUBLIC repository. Never reference client / consumer projects in anything published here. When a fix originates from a bug reported on a specific consumer site, do NOT name that project, its domains (
*.sk,.test), or any of its data (post titles, people's names, cities, CPT contents) in commit messages, PR titles/bodies, changelogs, code comments, or docs. Describe the bug and its reproduction generically (e.g. "picking three posts in a scrambled order"). Consumer-specific reproduction and verification stay in the consumer project's own private repo/notes. - Prefer minimal, contract-preserving edits.
- Preserve comments that explain operational intent, especially in setup scripts.
- Do not silently remove troubleshooting paths (1Password, env fallback, platform-specific behavior).
- Keep file paths and command examples consistent with
scripts/setup-corenaming. - Avoid editing unrelated files in large migrations.
- Always check and update documentation
- Throwaway scripts and captured output (diagnostics, screenshots, scratch
*.mjs) go in.artifacts/(gitignored) — never commit them. Only durable, reusable tooling belongs inscripts/(e.g.scripts/check-blocks-console.mjs,scripts/build-demo-page.mjs).
Before handoff, run relevant checks:
- Shell syntax for setup scripts:
find packages/webentor-setup -type f -name '*.sh' -print0 | xargs -0 -n1 bash -n
- Shell syntax for split mirror and release scripts:
bash -n scripts/split-webentor-setup.shbash -n scripts/split-webentor-starter.shbash -n scripts/release-tags.sh
- Version source consistency:
pnpm check:versions - Setup CLI syntax:
php -l packages/webentor-setup/src/webentor-setup.php
- JSON validity for changed
package.jsonor manifest files. - If starter touched, verify it contains no setup scaffolding (no
scripts/, no retired.webentor/). The one metadata file it does ship is.webikon/project.json, whose shapepnpm check:versionsenforces. - If blocks or
webentor-core/theme runtime changed, validate against the test-site:pnpm test:setup(first run) thenpnpm test:e2e; for block-editor changes also runnode scripts/check-blocks-console.mjs(see Testing Suite (E2E)).
- Breaking 1Password
.envfetch flow (OP_VAULT_ID+OP_ITEM_ID+env-valet). - Mixing project-specific hooks into
setup-core. - Releasing package changes without considering demo compatibility.
- Putting generated scaffolding files (
.env.setup,setup.sh, hooks, etc.) back intopackages/webentor-starter— the starter is a pure project skeleton.
If uncertain whether a change belongs in shared runtime or project customization:
- Put reusable behavior in
packages/webentor-setup. - Put project-specific behavior in hooks/env files outside
setup-core.