Visual Editor - v1.0.0
Troubleshooting
Common problems and the underlying constraints they surface, in rough order of how often they come up.
1. Editor doesn't appear
The Blade component rendered, but the React app didn't boot. Most likely causes, in order:
- Vite bundle not built. Run
npm run build(ornpm run devin development). The editor mounts from the package's compiled JS bundle, which Vite emits topublic/build/. - Peer dependencies missing. Tailwind CSS v4 + DaisyUI v5 must be loaded at the page level. The editor inherits them — without them, the React tree renders but is invisible.
- JS console errors. Open the browser console. Missing
core-datashim (the package aliases@wordpress/core-datato its own shim invite.config.ts— confirm your Vite config doesn't override the alias). - The mount point is hidden by CSS.
[data-ap-visual-editor]needs visible dimensions — wrap it in a layout that gives it height.
2. Core-data shim entities and missing data
The package ships its own @wordpress/core-data shim under
resources/js/visual-editor/vendor/core-data-shim.ts. The shim
implements a curated subset of selectors that Gutenberg expects:
getEntityRecord, getEntityRecords, getEditedEntityRecord,
canUser, getCurrentUser, getMedia, plus the resolver machinery
(hasFinishedResolution, isResolving).
Symptoms when the shim doesn't cover a code path:
- A block renders empty in the canvas with no error.
- The console shows
Cannot read properties of undefinedfrom a Gutenberg internal. - A core-data selector returns
nulland the block infinite-loops onisResolving.
Diagnosing. Add a debugger to the shim's getEntityRecord and see
what { kind, name, id } the block is asking for. If kind:name is
unregistered, the shim returns null — register the entity in
core-data-shim.ts or add a back-compat stub.
Fixing. New entities go into the entities array; new selectors go
into the selectors map. Both files are tested by vitest run —
adding a fixture for the new entity and asserting the shim returns it
catches regressions.
3. WordPress package upgrades
The package pins @wordpress/* to the versions listed in package.json.
Renovate / Dependabot is paused for these packages during V1.x —
mid-stream minor bumps invalidate weeks of UI work.
Upgrade procedure
- Decide to upgrade deliberately. Read the upstream
CHANGELOG.mdfor every@wordpress/*package being bumped. Note schema-version bumps, removed selectors, renamed components. - Update
package.jsonpins together. All@wordpress/*packages should move in lockstep — they share internal contracts and skewing them cross-version causes subtle runtime breakage. - Re-run the parity check.
npm run verify:parityrenders fixture trees through Blade + React + Vue renderers and diffs the output. Drift means a block fork's markup changed upstream. - Re-run upstream-diff.
npm run upstream-diffcompares each forked block'sregister()call to a fresh upstream registration and surfaces drift in attributes / supports / variations. - Decide on each drift. Adopt upstream change → update the fork +
bump its
upstream-state.json. Reject the change → annotate theupstream-state.jsonwith the rejection reason. - Schema-version check. If theme.json schema bumped, follow Global styles §8.
- Run the full test suite.
./vendor/bin/pest && npm test.
Symptoms of an unaudited upgrade
- New block variations appearing in the inserter that the team didn't agree to ship.
- Removed selectors causing core-data shim warnings on selector lookup.
- theme.json
versionvalidation errors onPUT /global-styles/{id}. @wordpress/componentsstyle regressions (the package overrides Gutenberg button/modal styling — restyling work needs to be redone against the new component internals).
4. theme.json schema upgrades
The pinned schema version (currently 3) is enforced by
UpdateGlobalStylesRequest — every PUT must include
version === 3. When the upstream schema bumps:
- Audit the new schema's added/removed top-level keys.
- Update
artisanpack.visual-editor.global_styles.schema_versionto the new version. - Update
resources/theme-json/default-base.phpdefaults. - Ship a migration that either rewrites existing user records into the new schema or documents a manual upgrade path for customized records.
- Update Global styles.
The pin exists precisely so this is conscious work — not silent drift on
npm update.
5. Theming quirks
The editor restyles @wordpress/components (Button, Modal, Notice, etc.)
to DaisyUI via the
@artisanpack-ui/react
component pack. Restyling is best-effort — some quirks:
- Modal layering. Gutenberg's modals use a portal at document root.
If the host page has its own portal root with a higher
z-index, the Gutenberg modal can render behind it. Bump the host portal toz-index: 60or lower. - Color tokens. The editor reads palette tokens from the active
global-styles record. If colors look wrong, check
/global-styles/cssin DevTools to confirm the expected variables are emitted. - Dark mode. Toggled by DaisyUI's
data-themeattribute on a parent element. The editor inherits the host page's theme; flipping themes inside the editor without a remount produces inconsistent paint states until the next interaction. - Avoid
@artisanpack-ui/reactPopover and Dropdown inside the editor. Both have known interactions with the editor's portal that freeze the canvas. Use inline manual patterns or@wordpress/componentsPopover / Dropdown directly.
Full theme contract: Theming.
6. "Unknown resource" on save
PUT /visual-editor/api/{resource}/{id}/content returns 404 with
"Unknown resource '{slug}'". The slug isn't in
config('artisanpack.visual-editor.resources') and no
ap.visual-editor.resources filter contributor added it.
Check:
php artisan config:clearif you recently edited the config.- The Blade component's
resource="..."attribute matches a registered slug. - If you're relying on cms-framework auto-registration, confirm cms-framework is installed and the version pair is compatible.
7. Saves succeed but content disappears on reload
The model is using HasBlockContent but $blockContentColumn doesn't
match an actual column on the model, or the column isn't cast to JSON.
Check:
- The migration added a JSON column with the expected name (default:
content). - If the model already had a
contentcolumn with a non-JSON cast (e.g.string), set$blockContentColumnto a different name or override the cast incasts().
8. Site editor returns 403
The default SiteEditorAccessGate is DenyByDefaultGate — fail-closed.
Either:
- Bind a permissive gate in your app service provider (see Content model §3 and Access Gate).
- Install cms-framework, which binds
CmsFrameworkInstallGateautomatically.
9. Renderer parity drift
npm run verify:parity fails after editing a block. The Blade, React,
and Vue renderers produce different HTML for the same block tree.
Common causes:
- Edited the
save.tsxmarkup but didn't update the corresponding Blade partial / React component / Vue component. - Static block has different default attribute coercion in two
renderers (e.g. one falls back to
'', another tonull). - Dynamic block's
render()output drifted from the React renderer's<DynamicBlock>fallback expectations.
Fix the renderer that disagrees with edit.tsx / save.tsx. The
parity script is the source of truth — if a fixture produces three
different HTML strings, the fixture is wrong or one of the renderers is
wrong, never the script.
10. Dev sandbox specifics
The /ve-sandbox route (npm run dev:sandbox) is a standalone editor
playground that doesn't talk to the API. Useful for block authoring in
isolation; will be removed post-V1.
If the sandbox crashes but the real editor works, you've probably hit a
sandbox-only regression — file under area:sandbox rather than
debugging the production editor.