Skip to content

fix(native-windows): switch to list tab on embedded editor back navigation - #952

Merged
epeicher merged 7 commits into
WordPress:trunkfrom
CookieDarb:fix/native-window-editor-back-nav
Oct 19, 2026
Merged

epeicher merged 7 commits into
WordPress:trunkfrom
CookieDarb:fix/native-window-editor-back-nav

Conversation

@CookieDarb

@CookieDarb CookieDarb commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

What?

Closes #951

Fixes the issue where clicking the back button (e.g. "View Pages" / "View Posts") inside an embedded "Add New" editor tab does not return to the list view.

Why?

Native windows (like Posts and Pages) embed administrative pages (such as Gutenberg's Add New editor) via synthetic iframes inside native tab containers rather than top-level shell windows. Because synthetic iframes lack the shell-managed Window.iframe binding that routes through src/window/iframe-bridge.ts, in-frame navigation events (os-iframe-admin-link) posted by the chromeless bridge were ignored.

When a user clicked the back button to return to the list view, the event was dropped, leaving the user unable to return to the item list from within the editor interface.

How?

  • src/native-windows.ts:
    • Added an os-iframe-admin-link listener in buildIframeContentRender() for synthetic iframe message dispatches.
    • Resolved remap targets using resolveNativeUrlRemapTarget( linkUrl ). If the link targets the current window (matching windowId or baseId), it delegates to the host app's opts.onAdminLink( linkUrl, tab ) callback instead of mutating DOM directly.
    • Delegates cross-window remaps to tryNativeUrlRemap( linkUrl ) and falls back unmapped links to windowManager.open().
  • src/desktop.ts:
    • Updated native URL remap definitions for desktop-mode-posts and desktop-mode-pages to provide a params hook returning { tab: tagged ? tagged : 'posts' } (or 'pages'), supplying the target tab dynamically.
  • apps/posts/parts/app.ts:
    • Passed onAdminLink: ( _url, tab ) => { activateTab( ctx, ui, tab ); return true; } to embedAdminPage, letting the Posts app handle its own tab switching.
  • docs/:
    • Updated docs/javascript-reference.md, docs/bridge-protocol.md, docs/examples/native-posts.md, and docs/api-index.md documenting opts.onAdminLink and remap landing tabs.
  • tests/:
    • Added targeted tests in tests/vitest/native-window-iframe-admin-link.test.ts covering same-window callback invocation, multi-instance window resolution via baseId, cross-window remap delegation, unmapped fallbacks, and teardown cleanup.
    • Added a dedicated test in apps/posts/posts.test.ts verifying that onAdminLink switches the Posts app back to the list tab.

Testing Instructions

  1. Open the native Pages or Posts window.
  2. Switch to the Add New tab to load the embedded Gutenberg editor.
  3. Click the back button ( "View Pages" / "View Posts") in the editor toolbar.
  4. Verify that the window smoothly switches back to the list tab.
  5. Verify that clicking an admin link pointing to another window or section correctly remaps or opens the corresponding window.

Use of AI Tools

AI assistance: Yes
Tool(s): Antigravity
Model(s): Gemini
Used for: Diagnosing the root cause, aligning the implementation and adding tests. Final review and edits were made by me.

Open WordPress Playground Preview

@epeicher
epeicher self-requested a review October 16, 2026 08:34
@epeicher

epeicher commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Thanks for tracking this one down, @CookieDarb! A dead back button in the embedded editor is a really good catch, and routing os-iframe-admin-link for synthetic iframes is the right gap to close.

The part I'd change is where the decision lives. Right now the shell finds .os-app-list, writes os-tabs.value and hard-codes the 'posts' tab, so the generic embedAdminPage code knows about one app's DOM (AGENTS.md, "Event-driven framework"). It also picks 'posts' for links like post-new.php or edit-tags.php?taxonomy=category, and compares the remap id against the live instance id, so a second Posts window focuses the first one. Could the shell resolve the target with resolveNativeUrlRemapTarget() and hand it to the host, which already has activateTab()? Roughly:

// src/native-windows.ts, os-iframe-admin-link branch
if ( e.origin !== window.location.origin ) {
	return; // same trust boundary as the neighbouring handlers
}
const target = resolveNativeUrlRemapTarget( linkUrl );
const tab = target?.params?.tab;
if ( target?.id === baseId && tab && cfg.onAdminLink?.( linkUrl, tab ) ) {
	cfg.onMessage?.( e.data ); // keep forwarding every message
	return;
}
// apps/posts/parts/app.ts
ui.editor = window.wp?.os?.embedAdminPage?.( host, url, {
	onAdminLink: ( _url, tab ) => {
		activateTab( ctx, ui, tab );
		return true;
	},
} ) ?? null;

The same-origin check matches the neighbouring handlers. With a new option on embedAdminPage, the embedAdminPage section in docs/javascript-reference.md and the os-iframe-admin-link row in docs/bridge-protocol.md need a line each too.

…editor

Add onAdminLink callback option to embedAdminPage so synthetic iframes inside native windows can delegate same-window back navigation to the host app's tab management rather than opening a second window.
@CookieDarb

Copy link
Copy Markdown
Contributor Author

Hey @epeicher, thanks for the detailed review! That made total sense.

I've refactored the PR along those lines:

  • Moved tab switching out of native-windows.ts into an onAdminLink callback on embedAdminPage, so the host app handles it via activateTab().
  • Added the origin check and resolved targets with resolveNativeUrlRemapTarget() (including baseId for multi-instance windows like posts-2).
  • Supplied target tabs dynamically via remap params instead of hardcoding 'posts'.
  • Updated docs and tests accordingly.

Also updated the PR description to match the same. Let me know what you think!

The admin-link routing returned early for links handed to another
native window and for links from another origin, so a window's
`iframeContent.onMessage` stopped seeing them. That broke the
documented contract that every message reaches `onMessage`, and a
plugin routing its own links could lose them or, on the fallback path,
open the link twice.

The routing now lives in `routeAdminLink()` and the handler always
falls through to `onMessage`, whichever way the link was routed.
Same-origin links only are routed, as before.

@epeicher epeicher left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @CookieDarb for addressing the comments! I have pushed some small fixes on top and test this, and it LGTM!

Video

CleanShot.2026-10-19.at.13.12.56.mp4

@epeicher
epeicher enabled auto-merge (squash) October 19, 2026 11:14
@epeicher
epeicher merged commit 1179f26 into WordPress:trunk Oct 19, 2026
8 checks passed
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.

Native Windows: Back navigation ("View Posts" / "View Pages") in embedded "Add New" editor is not responding

2 participants