Skip to content

[6.x] Legacy bridge - #19884

Draft
brianjhanson wants to merge 50 commits into
6.xfrom
legacy-bridge
Draft

brianjhanson wants to merge 50 commits into
6.xfrom
legacy-bridge

Conversation

@brianjhanson

@brianjhanson brianjhanson commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Lets a Craft 5-era control panel screen render inside the Inertia shell without its author changing anything, and ports the form controls those screens rely on to web components.

A plugin that hasn't been updated for Craft 6 still returns asCpScreen() responses, calls renderTemplate(), and extends _layouts/cp. All three now arrive at BridgedScreen as server-rendered strings for the shell's slots, so the screens draw correctly instead of losing their tabs, assets and JS.

Screen bridge

  • LegacyScreenFragments collects a legacy screen's fragments; BridgedScreen wraps them for cp/Screen.vue, which draws them into the shell's slots. RenderBridgedScreen middleware and _layouts/cp-fragments.twig cover the template and renderTemplate() routes into it.
  • A bridged screen is always a hard visit. Legacy JS needs jQuery, Garnish and cp.js loaded in source order before anything that uses them, which a client-side visit can't offer, so the rule is total rather than partial.
  • LegacyReadyShim holds ready-JS until the markup is mounted — Vue mounts a bridged screen after the document parses, so DOMContentLoaded has already fired. HtmlStack::deferInlineJs() wraps body-end inline JS to match.
  • PageLifecycle no longer drops assets for a position with no placeholder in the output; they go back on the stack instead.
  • Legacy admin tables render through LegacyAdminTable.vue.

Element index

  • A screen that is just {% extends "_layouts/elementindex" %} gets the new Vue index. The layout declares it, since it's the one place that knows the element type; a child that replaces the content block opts back out via a sentinel.
  • Its sources render into the secondary nav, with a Customize Sources item in an action menu at the foot of the subnav, matching Craft 5's sidebar. ContentIndexViewModel::sourceNavItems() builds them.
  • Navigation::sourceSubnav() is now public, so a plugin building its own nav section can hang an element type's sources off it the way the core sections do.

Tabs

  • LegacyTabsShim pairs a legacy screen's panes to <craft-tabs>, so a bridged screen gets the same tab component as the rest of the control panel. It returns null whenever it isn't certain, and the legacy strip covers that.
  • <craft-tabs> mirrors the selected tab in the location hash and selects from it on load, enabled by default. Saving keeps the current hash.
  • The tab strip is guttered with the content below it.

Form controls

  • Autosuggest and selectize inputs render as <craft-combobox> rather than Vue 2 instances, through craft.cp.autosuggest()/craft.cp.selectize() and FormFields::autosuggestFromConfig()/selectizeFromConfig(). Craft 5's variable shapes are still accepted.
  • Field toggles drive from a combobox, resolving data.boolean so an environment variable that parses to a boolean still toggles.
  • Indicator options moved onto fill, with appearance and icon color alongside.
  • An editable table initializes once it's actually laid out, rather than on the next window resize.

Plugin API and Yii adapter

  • getVersion() stays out of the Craft 6 plugin API. The adapter keeps it, reading and writing $version and logging a deprecation, so a plugin author gets told rather than guessing.
  • A legacy plugin's stored settings load in full, and a legacy CP URL rule for a plugin's settings path is honoured.
  • Legacy type-registration events are deferred until first read.
  • Craft::getAlias() returns a non-alias value unchanged; the Craft 6 utility base class extends the legacy one.

Note for review

The last commit pulls two commits from #19665 — Cp::sharedModules(), resources/js/vue.ts and resources/js/elements.ts — which publish the control panel's Vue runtime and element index to plugin bundles through an import map. A plugin that externalizes those specifiers (Commerce does) can't boot without them. They'll want dropping in favour of #19665 whenever that lands.

Testing

Exercised against Commerce and a Craft 5-era plugin on a 6.x install: plugin settings screens with tabs and editable tables, autosuggest and selectize fields, and a bridged product index with its sources in the subnav.

Staring to port the Shopify plugin and had to make these undocumented
changes
# Conflicts:
#	CHANGELOG.md
#	resources/js/common/layouts/screens/PageScreen.vue
#	resources/templates/_includes/forms/editableTable.twig
#	src/Cp/Html/ContentHtml.php
#	src/Cp/Navigation.php
#	src/Markdown/Flavors/GfmFlavor.php
#	tests/Unit/Markdown/MarkdownTest.php
@brianjhanson brianjhanson changed the title Legacy bridge [6.x] Legacy bridge Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

📚 Storybook previews

@craftcms/ui — open Storybook

Changed components:

resources/js — open Storybook

No changed components detected in this Storybook.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants