Visual template editor for Sunrice CMS: edits the site's Blade files in the browser. A plugin for the Sunrice panel.
sunrice/visual-editor is a Laravel package for visual template editor for sunrice cms: edits the site's blade files in the browser. a plugin for the sunrice panel..
It currently has 0 GitHub stars and 3 downloads on Packagist.
Install it with composer require sunrice/visual-editor.
Discover more Laravel packages by sunrice
or browse all Laravel packages to compare alternatives.
Last updated
A visual template editor for Sunrice CMS, as a separate package: a plugin for the Sunrice panel (sunrice/admin), beside the CMS (sunrice/cms) whose templates it edits.
It works on the Blade files themselves: no copy of a template in the database and no second file beside it. A developer can keep editing the same file in a code editor. The editor reads a Blade file into a tree, changes one thing, and writes the file back with nothing else disturbed.
| | |
| --- | --- |
| Installation | requiring it, turning it on, settings, updating |
| How it works | lossless or code, what is checked before a save, how the page knows its templates |
| Fields and variables | what the code's completions offer at each place, and the Blade they write |
| Styles | the custom CSS, devices, sites that build their CSS |
| Permissions | open, edit, code, build; where the line is and how it is held |
| AI agents | visual_editor_get_template_tree and visual_editor_edit_template |
| Limits | what it does not do |
What the package is meant to do is in SPEC.md; how it was built, task by task, is in EXECUTION_PLAN.md. What each version brought is in CHANGELOG.md.

The screen has three parts: on the left the templates of the page and the open one's layers, a tab each; the page in the middle; on the right the code and the site's custom CSS and JS. The top bar links to the entry's content in the admin.
See a page's templates and click through them.
Edit the template as code, beside the page.
storage/app/sunrice-cms/template-backups/, beside the CMS's own copies.The CMS's fields and variables, in the code's completions.
$values[' the fields of the page, after $entry-> what every entry
has, after {{ everything that can be printed.$values (the fields of its collection, listing page,
taxonomy…) and its $entry or $term, a block's fields, the item of a
loop, cms_page(), $locale, global sets (cms_global()) and menus
(cms_menu()), in the text as typed, saved or not. A word typed first
narrows it.<img> or a link to
the file, a link as an <a>, a list as a loop, a yes-or-no as a
condition. All of it draws without an error when the field is empty, or
is not there at all; a field only some collections have says so.The page's content, a click away.
A stylesheet and a script of the site's own.
public/cms/custom.css, written as
text and saved with Ctrl+S.@stack('head'). The
deployed site has it too (Styles).public/cms/custom.js, loaded
(deferred) on every page through the layout's @stack('head'). It runs
in visitors' browsers, so writing it takes visual-editor.code
(Styles).Every device side by side.

widths).Room for the page.
Permissions of its own: who may open the editor, who may change things, who may write code (Permissions).
Tools for AI agents, on the panel's MCP server.
visual_editor_get_template_tree reads a template as the tree the
editor shows, and visual_editor_edit_template changes it by
operations: add a class, set a text, insert, move, wrap. An agent makes
a small checked change where the CMS's cms_write_template would
rewrite the whole file.visual-editor.edit, and Blade or PHP of the agent's own takes
visual-editor.code, by the same check as for a person. Changing also
takes the site's leave for agents to change templates at all, the CMS's
sunrice-cms.ai.templates (SUNRICE_AI_TEMPLATES=true).A site that builds its CSS (Tailwind through Vite) is styled by its
classes. With npm run dev running a class added in the editor shows at
once; otherwise after a build, or before one with the preview turned on
(Styles). With a build
command set, Build in the top bar runs it for whoever holds
visual-editor.build, says Build needed once a template has changed,
and shows what the command printed when it fails.
The package is not on Packagist yet. To try it, run the demo site from this
repository (composer serve, below), or install it in a Sunrice site from
this repository: Installation. It is a plugin of
the panel, added in the app's panel provider after the CMS:
->plugin(CmsPlugin::make())->plugin(VisualEditorPlugin::make()).
It is a development tool: on when APP_ENV is local, off otherwise, and
SUNRICE_VISUAL_EDITOR=true turns it on for a staging copy. Who may open
it, change things and write code is decided by three
permissions of its own, and who may run the site's
build by a fourth.
Opening it. Visual editor in the panel's sidebar, under Structure, Edit with Visual Editor in the ⋮ menu of an entry's page, Edit … in the visual editor in the command palette (Cmd/Ctrl+K), or directly:
| Address | Opens |
| --- | --- |
| /admin/visual-editor | the homepage |
| /admin/visual-editor?url=/about | that page of the site |
/admin stands for the panel's path. The url has to be a path on the
site; anything else opens the homepage. A link like the second one can be put
wherever it is handy, for instance in a bookmark for the page being worked on.
The approach holds. Full numbers are in spike/RESULTS.md.
| Templates | Files | Open and print back byte for byte | Refused | Edits passed |
| --- | ---: | ---: | ---: | ---: |
| Sunrice starter templates (stubs/templates) | 19 | 19 | 0 | 3,695 of 3,695 |
| Sunrice package views (defaults, components, error pages) | 11 | 11 | 0 | 2,031 of 2,031 |
| Laravel framework's own Blade views | 129 | 127 | 2 | 11,733 of 11,733 |
| Edge cases written for this test | 20 | 18 | 2, as intended | 1,132 of 1,132 |
The two Laravel files refused are test fixtures that are malformed on purpose.
In the 19 starter templates, 85% of the 379 nodes are fully visual (elements,
text, conditions, loops, @switch arms, includes, plain field echoes,
translation strings), 5% are echoes of a longer PHP expression, 4% are
elements with a directive inside the tag, and 5% are code, mostly @php
lines. Code covers 6.5% of the bytes. It was 17% while @switch was kept as
code: the form block and the filter form are now edited arm by arm.
For every template:
Template::open() refuses the file otherwise.Operations:
add and remove a class, add, change and remove an attribute, rename the
element, retype text, change an echo's expression and escaping, change a
directive's arguments, rewrite a code node, insert before and after,
append, delete, duplicate, reorder, move into another element, wrap and
unwrap.
An edit passes when the output is exactly the expected bytes, computed
from the original file and the node's offsets, opens as a tree again, and
compiles with Laravel's Blade compiler to valid PHP. (For the few edits
whose new lines depend on the file's indentation, the content must match
exactly and the layout is covered by unit tests.) Typed text must also
read back as typed and add nothing for PHP to run.stray-… edge fixtures); a file whose Blade can't be read is refused
(locked-…).@if/@else/@endif and component tags, and treats everything else as
plain text. The HTML layer is written here: HtmlLexer and TreeBuilder.BladeScanner converts them;
the multibyte fixture and the filter form (which contains "—") cover this.@else{{ $entry->title }}@endif leaves @else@endif, which Blade no longer
reads as two directives, so the file stops compiling. This turned up in
partials/card.blade.php and two package views. Seams now detects it and
puts one space at the join.@if and closed in another, an element left open across @else, end tags
left out (<li> without </li>), a layout split over a header and a
footer file. None of the 30 Sunrice templates do this, but hand-written
Blade often does. The tree keeps each such tag as it is written, marked
stray: it is shown as code and stays where it is, and everything around it
is edited as usual (the stray-… fixtures). An edit is refused if it would
leave more of them than there were.@if whose @endif sits inside a tag (the
locked-… fixtures). Such a file is edited as code.Phase 6 changes CSS files the way the editor changes Blade files: one declaration, in place. Before building it, the same question was put to CSS. Full numbers are in spike/CSS_RESULTS.md.
| Stylesheets | Files | Rules | Declarations | Found in place | Changes passed | | --- | ---: | ---: | ---: | --- | ---: | | The starter theme, as written and minified, and edge cases | 6 | 230 | 593 | all | 2,095 of 2,095 | | normalize.css, Pico, Bulma, Bootstrap, as written and minified | 6 | 10,709 | 24,124 | all | 5,409 of 5,409 |
src/Css/Stylesheet.php only finds where rules and
declarations are, and cuts into the text there.*zoom,
::-moz-focus-inner). None changed anything else.red; } body { display: none is refused, as is
anything that opens a quote, bracket or comment it doesn't close.@import brings
in is not followed.tests/Feature/EditorRenderTest.php).composer spike) runs without a Laravel application, so it
leaves component tags (<x-sunrice-cms::entries>) as text when compiling. The
Pest tests run the same edits inside an application, where they compile.@php($image = …) line below the place that uses $image still compiles.composer install
composer test # Pest: the round-trip checks, the package, a seeded Sunrice site (the panel and the CMS)
composer spike # the round-trip report: adds Laravel's own views, rewrites spike/RESULTS.md
composer spike:css # the same for stylesheets (spike/css-fetch.sh first adds Bootstrap, Bulma, Pico)
composer lint # Pint
composer analyse # Larastan
npm install
npm run typecheck
npm run test:frontend
npm run build # resources/js and resources/css → dist/
npm run test:bridge # the frame's script in Chrome, against a fixture page
npm run test:css # the stylesheet reader's rules and changes, put to Chrome
dist/ is committed. On a pull request, CI rebuilds it and commits the
result to the branch, so it doesn't have to be built by hand.
To look at the site the tests use, with the panel at /admin (sign in as
[email protected] / Sunrice-Demo-2026!) and the editor at /admin/visual-editor:
composer serve # builds the workbench (assets, templates, database, demo content) and serves it
To try the installation page on a new Laravel application (it makes one in a temporary directory, installs the Sunrice panel and CMS and this checkout into it, checks the site as installed and removes the package again):
.github/install-check.sh
The same on a site whose CSS is Tailwind built by Vite (it needs Node and npm, and Chrome for its last part): a class added in the editor reaches the CSS by a build, run by hand and from the editor's Build button, and by the dev server, and the editor is tried in a browser with the dev server running.
.github/vite-check.sh
Where Packagist and npm can't be reached but GitHub can, everything above
still runs: the ci/lock branch of this repository holds a composer.lock
that CI resolved, and a script that fetches each locked package from GitHub
and installs vendor/ from the copies. Its README says how.
src/
Blade/ the template tree
BladeScanner.php Blade constructs, from stillat/blade-parser, as atoms with byte offsets
HtmlLexer.php tags, comments and doctypes around them, with exact ranges
TreeBuilder.php the tree; strict about nesting, throws Unsupported otherwise
Node.php a tree node; owns its source text
Template.php open(): tree, or Unsupported; version, find(id), toArray()
Classifier.php visual / field / label / expression / mixed / code
Rules.php what may be done to a node
TreeData.php the tree as plain data for the editor's panels
Edit.php every edit, in one place
Operation.php an edit as plain data: what the editor's screen and AI agents send
Operations.php applies operations; hands back a file only if it opens and compiles
Syntax.php whether a template compiles
Variables.php the variables a template needs to be given
Attributes.php edits to an opening tag: attributes, classes, the tag name
Content.php edits to text, echoes, directive arguments; a node rewritten as code
Structure.php insert, move, duplicate, delete, wrap, unwrap
Layout.php the indentation around a node, so new lines match the file
Fragment.php a snippet read into nodes
Inert.php typed text as text Blade won't run
Seams.php keeps directives readable where an edit joins two pieces
Css/ the theme's stylesheets, read for where things are in them and changed in place
Stylesheet.php the rules of a file; a value set, a declaration added or taken out, a rule added
Rule.php a rule: its selectors, its declarations, the at-rules it is inside
Declaration.php one `property: value`, with the place of each part
Changes.php applies changes sent as plain data; all of a list or none
Selector.php the selector for a class, and whether a selector mentions it
ThemeRepository.php the CSS files in public/cms, the editor's own and the custom CSS
Styles.php what the stylesheets say about a class
Guard/ what someone who may not edit code can't get into a template (CodeGuard)
Mcp/ the tools for AI agents: visual_editor_get_template_tree, visual_editor_edit_template
Templates/ the files the editor works with: the site's templates and the CMS's views
Insert/ Library: what can be inserted, for this site (the API and AI agents)
Actions/ SaveTemplate: the only place a template is written; CopyPackageView
Rendering/ a page rendered for the editor: elements marked with their template;
CustomCss, CustomJs: the custom CSS and JS on every page
Http/ the editor's screen, and its API for templates (Controllers/Api)
Support/ Access: who may use the editor; SunriceBridge: the one file that
reads the CMS (templates, which page uses which, field definitions)
VisualEditorPlugin.php what it adds to the panel: permissions, routes, its page, tools
VisualEditorServiceProvider.php its config, published files, and what it adds to the site's pages
config/ sunrice-visual-editor.php
routes/admin.php routes inside the panel
resources/js, css/ the editor UI (the panel's React and Inertia, window.Sunrice.modules), built into dist/:
Editor.tsx the screen, panels/ the tree and the tabs beside the page,
canvas/ what is drawn over it, lib/ the API and the frame
resources/js/bridge/ the script in a page shown in the editor's frame, built into dist/bridge.js
resources/js/code/ the code editor (CodeMirror), built into dist/code-editor.js and fetched when code is first shown
spike/ the round-trip reports (templates, stylesheets) and their results
tests/ Pest tests, Frontend/ unit tests and the Browser/ check; fixtures/ holds
site/ (the demo site's block templates), sunrice-starter and sunrice-package
(Blade of Sunrice before 0.1, kept for the round-trip tests) and edge
workbench/ the Testbench app: user model, panel provider, migrations, demo content