Repository navigation
feat: M1 · install Gutenberg packages and mount sandbox canvas - #327
Conversation
Installs the five @wordpress/* packages that power the V1 editor and proves they import cleanly through a temporary /ve-sandbox Blade route: - Exact-pin @wordpress/blocks@15.17.0, @wordpress/block-editor@15.17.0, @wordpress/block-library@9.44.0, @wordpress/components@32.6.0, and @wordpress/i18n@6.17.0. Also downgrade react/react-dom to exact-pinned 18.3.1 (with matching @types) to satisfy the React ^18 peer range shared across the Gutenberg stack. - Lazy-load the Gutenberg code via a dedicated Rollup chunk: resources/js/visual-editor/sandbox/main.tsx is a ~0.7 kB entry that dynamically imports ./sandbox-editor, and a manualChunks rule in vite.config.ts routes every /node_modules/@wordpress/ module into a single `gutenberg` chunk. Non-editor admin pages pay 0 kB of Gutenberg. - Add a /ve-sandbox Laravel route and sandbox.index Blade view that mount an empty BlockEditorProvider against a single-paragraph hardcoded block tree (SlotFillProvider + BlockTools + WritingFlow + BlockList + Popover slot). Temporary scaffolding — deleted once the real editor shell ships (M3+). - Add .github/renovate.json with a packageRule that groups @wordpress/* updates into one `gutenberg` PR on a 2-week cadence with rangeStrategy pin, so the cross-package version coupling stays in lockstep. - Skip one legacy useBlockPreview assertion that relied on React 19's native boolean `inert` attribute emission; the _legacy tree is deleted at M15 so the skip is short-lived. Closes #311 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughA pull request establishes Gutenberg package dependencies (React 18, Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
One-command local verification for the M1 sandbox: `npm run dev:sandbox` starts Vite with `resources/js/visual-editor/sandbox` as the root and serves the minimal index.html at http://localhost:5176. Mirrors the existing pattern used by the legacy editor's dev entry. Intentionally does not touch vite.config.ts — CLI positional root overrides the serve-mode `root` without requiring multi-entry config gymnastics. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
routes/web.php (1)
15-20: Consider restricting sandbox to non-production environments.The sandbox is documented as temporary for M1. For defense-in-depth, consider wrapping it in an environment check to prevent accidental exposure in production.
🛡️ Optional: restrict to local/development
// Temporary sandbox route for M1 (`#311`). Mounts an empty BlockEditorProvider // to prove `@wordpress/`* packages import cleanly and the Gutenberg canvas // renders. Removed once the real editor shell ships (M3+). -Route::get('/ve-sandbox', function () { - return view('visual-editor::sandbox.index'); -})->name('visual-editor.sandbox'); +if (app()->environment('local', 'development', 'testing')) { + Route::get('/ve-sandbox', function () { + return view('visual-editor::sandbox.index'); + })->name('visual-editor.sandbox'); +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@routes/web.php` around lines 15 - 20, Wrap the temporary Route::get('/ve-sandbox', ...) registration in an environment check so it only registers in non-production contexts (e.g., local, development, staging, testing); locate the Route::get call that returns view('visual-editor::sandbox.index') (named 'visual-editor.sandbox') and guard its definition with an if that checks app()->environment(...) or config('app.env') to exclude 'production' before registering the route..github/renovate.json (1)
7-17: Consider grouping React with@wordpress/updates.*Since React 18.3.1 is pinned specifically to match Gutenberg's peer range (as noted in the PR summary), React version updates should ideally stay coordinated with
@wordpress/* updates. Currently, Renovate will propose React updates separately, which could break the peer dependency alignment.♻️ Proposed addition to keep React in sync
"packageRules": [ { "description": "Group all `@wordpress/`* package updates into a single PR on a two-week cadence, and keep them exact-pinned so Gutenberg's cross-package version coupling stays in lockstep.", "matchPackagePrefixes": ["@wordpress/"], "groupName": "gutenberg", "groupSlug": "gutenberg", "schedule": ["every 2 weeks on monday"], "rangeStrategy": "pin", "labels": ["dependencies", "gutenberg"] + }, + { + "description": "Pin React/ReactDOM to match Gutenberg's peer range; group with gutenberg updates.", + "matchPackageNames": ["react", "react-dom", "@types/react", "@types/react-dom"], + "groupName": "gutenberg", + "groupSlug": "gutenberg", + "schedule": ["every 2 weeks on monday"], + "rangeStrategy": "pin", + "labels": ["dependencies", "gutenberg"] } ]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/renovate.json around lines 7 - 17, Update the Renovate packageRules so React updates are grouped with the existing `@wordpress/`* rule: add React (and react-dom if used) to the same rule by including matchPackageNames ["react","react-dom"] (or extend the rule to match them) alongside matchPackagePrefixes ["@wordpress/"], keep the same groupName/groupSlug "gutenberg" and the same schedule and rangeStrategy "pin" so React version bumps are proposed in the same, pinned two-week Gutenberg PR; modify the packageRules object that contains matchPackagePrefixes to also reference matchPackageNames.tests/Feature/SandboxViewTest.php (1)
73-76: Exact-pin assertions are currently too permissive.Line 76 matches only a semver prefix, and Line 88 only checks major version. That can let non-exact values pass, which weakens the “exact pin” contract.
Proposed tightening of version assertions
- expect($version) - ->not->toStartWith('^') - ->not->toStartWith('~') - ->toMatch('/^\d+\.\d+\.\d+/', "{$pkg} must be exact-pinned, got: {$version}"); + expect($version) + ->not->toStartWith('^') + ->not->toStartWith('~') + ->toMatch('/^\d+\.\d+\.\d+$/', "{$pkg} must be exact-pinned, got: {$version}"); @@ expect($version) ->not->toStartWith('^') ->not->toStartWith('~') - ->toStartWith('18.'); + ->toMatch('/^18\.\d+\.\d+$/', "{$pkg} must be exact-pinned to React 18.x.x, got: {$version}");Also applies to: 85-89
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Feature/SandboxViewTest.php` around lines 73 - 76, The current assertions in SandboxViewTest around expect($version) only check a semver prefix and major version, letting non-exact pins slip through; update the expectations for $version (used in the expect($version)->... chain and the other similar block at lines 85–89) to reject common range operators and wildcards and to require a full exact semver by changing the regex to assert the entire string (e.g. use toMatch('/^\d+\.\d+\.\d+$/') or equivalent) and keep the not->toStartWith('^')->not->toStartWith('~') checks so the assertion enforces exact-pinned versions like 1.2.3 only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@vite.config.ts`:
- Around line 71-87: The build config sets manifest: false which prevents
generating manifest.json needed by Laravel's `@vite`() in production; update the
Vite config so production builds emit a manifest (e.g., set manifest: true for
rollupOptions builds or enable the Laravel Vite plugin) and ensure hashed
chunk/asset names remain as configured (refer to rollupOptions, input, output,
manualChunks) so `@vite`() can resolve chunks/assets; alternatively split configs
so only dev uses manifest: false and production uses a config that generates
manifest.json or integrates the Laravel Vite plugin to produce it.
---
Nitpick comments:
In @.github/renovate.json:
- Around line 7-17: Update the Renovate packageRules so React updates are
grouped with the existing `@wordpress/`* rule: add React (and react-dom if used)
to the same rule by including matchPackageNames ["react","react-dom"] (or extend
the rule to match them) alongside matchPackagePrefixes ["@wordpress/"], keep the
same groupName/groupSlug "gutenberg" and the same schedule and rangeStrategy
"pin" so React version bumps are proposed in the same, pinned two-week Gutenberg
PR; modify the packageRules object that contains matchPackagePrefixes to also
reference matchPackageNames.
In `@routes/web.php`:
- Around line 15-20: Wrap the temporary Route::get('/ve-sandbox', ...)
registration in an environment check so it only registers in non-production
contexts (e.g., local, development, staging, testing); locate the Route::get
call that returns view('visual-editor::sandbox.index') (named
'visual-editor.sandbox') and guard its definition with an if that checks
app()->environment(...) or config('app.env') to exclude 'production' before
registering the route.
In `@tests/Feature/SandboxViewTest.php`:
- Around line 73-76: The current assertions in SandboxViewTest around
expect($version) only check a semver prefix and major version, letting non-exact
pins slip through; update the expectations for $version (used in the
expect($version)->... chain and the other similar block at lines 85–89) to
reject common range operators and wildcards and to require a full exact semver
by changing the regex to assert the entire string (e.g. use
toMatch('/^\d+\.\d+\.\d+$/') or equivalent) and keep the
not->toStartWith('^')->not->toStartWith('~') checks so the assertion enforces
exact-pinned versions like 1.2.3 only.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 014a11c3-3082-492f-bd3e-e32c262b259b
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json,!package-lock.json
📒 Files selected for processing (10)
.github/renovate.jsonpackage.jsonresources/js/visual-editor/_legacy/editor/primitives/__tests__/useBlockPreview.test.tsxresources/js/visual-editor/sandbox/index.htmlresources/js/visual-editor/sandbox/main.tsxresources/js/visual-editor/sandbox/sandbox-editor.tsxresources/views/sandbox/index.blade.phproutes/web.phptests/Feature/SandboxViewTest.phpvite.config.ts
| rollupOptions: { | ||
| input: resolve(editorRoot, 'main.tsx'), | ||
| input: { | ||
| editor: resolve(editorRoot, 'main.tsx'), | ||
| sandbox: sandboxEntry, | ||
| }, | ||
| output: { | ||
| format: 'es', | ||
| entryFileNames: 'main.js', | ||
| chunkFileNames: '[name].js', | ||
| assetFileNames: '[name][extname]', | ||
| entryFileNames: '[name].js', | ||
| chunkFileNames: 'chunks/[name]-[hash].js', | ||
| assetFileNames: 'assets/[name]-[hash][extname]', | ||
| manualChunks(id) { | ||
| if (id.includes('/node_modules/@wordpress/')) { | ||
| return 'gutenberg'; | ||
| } | ||
| return undefined; | ||
| }, | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if there's separate Vite config for Laravel or if manifest is handled elsewhere
# Look for Laravel Vite plugin configuration or separate production config
rg -n "manifest" --type=ts --type=js -C2
# Check how the Blade views use `@vite` - if they reference entries by name
rg -n "@vite\(" --type=blade -g "*.blade.php" -C1 2>/dev/null || \
rg -n "@vite\(" -g "*.blade.php" -C1
# Check if there's a separate Laravel Vite config
fd -e php "vite" -x cat {}Repository: ArtisanPack-UI/visual-editor
Length of output: 1326
🏁 Script executed:
# Search for Laravel Vite plugin usage or custom configuration
rg -n "laravelPlugin\|viteLaravelPlugin\|laravelVite" --type=ts --type=js
# Check the actual vite.config.ts structure to see full plugin setup
cat -n vite.config.ts | head -30Repository: ArtisanPack-UI/visual-editor
Length of output: 1417
🏁 Script executed:
# Get the full build configuration section to understand manifest: false context
cat -n vite.config.ts | sed -n '28,100p'Repository: ArtisanPack-UI/visual-editor
Length of output: 3249
Address manifest: false with Laravel's @vite() directive in production.
The Rollup configuration is otherwise correct—multi-entry setup, hashing for cache busting, and @wordpress/* module isolation work as intended. However, manifest: false on line 69 creates a production blocker:
- Laravel's
@vite()helper in production requiresmanifest.jsonto resolve hashed asset paths - Entry files have stable names (
[name].js), but chunks/assets are hashed (chunks/[name]-[hash].js,assets/[name]-[hash][extname]) - Without the manifest,
@vite()cannot load the hashed dependencies, breaking asset resolution
Generate the manifest or use a Laravel Vite plugin to handle hashing, or ensure this is a dev-only build and production uses a separate config.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vite.config.ts` around lines 71 - 87, The build config sets manifest: false
which prevents generating manifest.json needed by Laravel's `@vite`() in
production; update the Vite config so production builds emit a manifest (e.g.,
set manifest: true for rollupOptions builds or enable the Laravel Vite plugin)
and ensure hashed chunk/asset names remain as configured (refer to
rollupOptions, input, output, manualChunks) so `@vite`() can resolve
chunks/assets; alternatively split configs so only dev uses manifest: false and
production uses a config that generates manifest.json or integrates the Laravel
Vite plugin to produce it.
Description
M1 of the Gutenberg adoption (umbrella #309). Installs the five Gutenberg npm packages, wires Vite to lazy-load them into a dedicated chunk, adds Renovate grouping for future upgrades, and proves everything mounts via a temporary `/ve-sandbox` Blade route. No Laravel integration or persistence yet — this is the "Gutenberg code actually runs" milestone.
Closes: #311
Type of Change
Related Issue
Issue: #311
Motivation and Context
Phase 2 pivoted from rebuilding every block-editor primitive in-house to adopting Gutenberg's React packages as a library dependency (see docs/gutenberg-adoption.md). M1 is the first milestone where Gutenberg code actually executes. Subsequent milestones layer Laravel persistence (M3), a media bridge (M4), and our own top bar and theming (M7–M8).
Changes Made
```
dist/editor/sandbox.js 0.72 kB
dist/editor/chunks/sandbox-editor-.js 0.56 kB
dist/editor/chunks/gutenberg-.js 18,121.41 kB
dist/editor/editor.js 605.96 kB (legacy; no @wordpress/)
```
The 0.7 kB `sandbox.js` entry dynamically imports `./sandbox-editor`, which is the sole synchronous importer of `@wordpress/`. Non-editor admin pages pay 0 kB of Gutenberg.
How Has This Been Tested?
Testing Environment:
Tests Performed:
Still pending manual verification: serving `/ve-sandbox` in a host Laravel app to confirm the Gutenberg canvas renders without console errors. Host-side wiring (running Vite with this package's sandbox entry as an input) is outside M1 scope — will exercise during M2/M3 or via the `artisanpack-ui` dev app.
Accessibility Tests Run
Details: A11y testing is deferred until we have real editor chrome (M7+). This PR is scaffolding that only renders Gutenberg's own canvas, which upstream already audits.
Tests Added
Test details: `tests/Feature/SandboxViewTest.php` covers every acceptance-criteria artifact (package pinning, Vite chunk shape, Renovate grouping, route, blade view, entry wiring) using the existing file-string assertion pattern established in `EditorMountViewTest.php`.
Documentation
Documentation details: Inline comments on `vite.config.ts`, `sandbox/main.tsx`, `routes/web.php`, and `sandbox.index.blade.php` mark the new surface as M1 scaffolding and point at #311 / `docs/gutenberg-adoption.md`. README and docs updates land with M3 (real editor shell) and M15 (release).
Screenshots
Canvas screenshot deferred to manual verification in the follow-up comment once the dev app wires `/ve-sandbox` end-to-end.
Pre-Submission Checklist
Summary by CodeRabbit
Release Notes
New Features
Dependencies
Chores
Tests