Repository navigation
Conversation
|
Claude finished @fmontes's task in 2m 41s —— View job Code Review
This PR is test + spec only — the 9 New IssuesNo issues found. Notes (non-blocking)
Looks good to proceed to the implementation (Green) phase. · |
|
Green half landed and the PR is out of draft. Nine commits on top of the Red tests ( What I checked on my own run, from
Three things worth a reviewer's eye:
History note: Manual passes still owed on |
…gh the side panel (#38303) The page editor is the largest surface to move onto the side-panel opener, so its tests now describe that before any code changes. Every editor entry point (pencil, copy-in-page, quick edit's full editor, the Edit Source fallback, URL-map edit, palette drop, the content selector's create, SDK edit and create) sets one $editRequest bound through injectEditContentPanel, naming the site, the portlet and, off the default variant, the experiment variant, and never choosing an editor. The edit binding places a drop's new contentlet once on the first save and only reloads after that, warns and stays open on a full container, loads a new URL map or reloads after an edit, asks a headless client to reload after an SDK edit, and reloads on language change, delete and close. Add content, add widget, translate and the SDK's reorder menu open the generic legacy-screen panel through $legacyScreenRequest, and the form selector opens as panel content. The actions handler swaps its dialog dependency for openContent and openLegacyScreen callbacks. The shell opens the page's Properties through the opener off the content route and refreshes on its saved and closed. The two URL builders get unit cases with the dialog store's exact query strings, as pure functions in utils. Every new or rewritten case fails against the current code on the missing request signals, callbacks or builders; every kept case passes. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) The add-content selector and translate screens move out of the page editor's dialog into the generic legacy-screen side panel, so the URLs the dialog store built from its own state become pure functions in utils: createAddContentletUrl(containerId, acceptTypes, languageId, variantName) and createTranslatePageUrl(page, newLanguage, variantName, siteId), producing exactly the query strings the store did. The store's copies go with the dialog later in this layer. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dialog (#38303) The actions handler no longer reaches into the page editor's dialog. ActionsHandlerDependencies trades `dialog` for two callbacks: openContent(request) and openLegacyScreen(url, title). editContentlet() now builds an edit request from the contentlet the SDK sent, so a new-editor type gets the new editor instead of always the legacy dialog; createContentlet() builds a create in the page language; and reorderMenu() opens the reorder screen as a legacy screen. Both requests are completed by withPageContext, a pure util the editor uses too, so every request the page editor opens names the same site, portlet, fallback language and, off the default variant, the experiment variant. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…request (#38303) The page editor stops choosing an editor and stops opening the centered dialog. The pencil, copy-in-page, quick edit's full editor, the Edit Source fallback, URL-map edit, a palette drop and the content selector's "create new" all set $editRequest, bound through injectEditContentPanel, completed with the site, the portlet, the page language and, off the default variant, the experiment variant (withPageContext). The side-panel flag read, the type lookup and the four dialog helpers go. A create opened from a drop or the selector remembers where it was asked for: the first save places the new contentlet once and saves the page, a duplicate or a full container is reported with the panel left open, and later saves only reload. An edit loads the content's new URL when its URL map changed, else reloads, with no fetch since the saved contentlet is complete; an SDK edit also asks the client to reload. A language switch loads that language; a delete and every close reload, the close being the safety net for legacy workflow actions. The add-content, form, translate and reorder screens still go through the dialog; they move in the next commit. One approved spec case counted quick edit's own type lookup, which selecting a contentlet triggers; it now clears the spy after the selection renders and asserts only what opening the full editor does. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#38303) The page editor no longer mounts its iframe dialog. The content selector a container's "+" opens (content or widgets), the translate screen and the SDK's reorder screen load in the generic legacy-screen panel through $legacyScreenRequest, with the URLs from the new utils. The editor reacts to what those screens report through legacyEvent: a selection is placed and the selector closes (a duplicate or a full container is reported and it stays); "create new" closes the selector and opens a create in the Edit Content panel that places the new contentlet in the same container; a translation saved or a language switched loads that URL and language, as the dialog did from htmlPageReferer; the reorder screen's save, error and cancel keep their toasts, reloads and the client reload. The form selector opens as side panel content: a selected form is resolved, placed and saved, then its panel closes; a form that cannot be resolved reports the error and the panel stays. handleNgEvent, onCustomEvent, the dialog view child, $showDialogs and the inode-fetching URL-map reload go. One approved spec case loaded the language-1 page, whose own URL map overrides the referer; it now loads the language-2 page as the dialog case it replaces did. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… panel (#38303) On the layout, rules and experiments routes there is no editor to open the page's properties, so the shell kept a second copy of the iframe dialog for it. It now opens the page through DotEditContentOpenerService with its own injector, completed by withPageContext like every other page-editor request. A save can change the page's URL, so it loads the page at its new URL (its URL map when it has one) or reloads it, and refreshes the workflow actions; a close reloads. On the content route Properties still goes through the editor. The dialog mount, its view child, handleNgEvent, handleSavePageEvent and reloadFromDialog go, and the shell spec drops the dialog stub it only needed while the shell mounted the dialog. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nel flag read (#38303) Every flow the dialog hosted now opens in the side panel, so the dialog component, its store, its spec and DotEmaWorkflowActionsService (the dialog's only consumer of it) go, with the dialog state types (EditEmaDialogState, DialogAction, DialogType, DialogForm, the create and add action payloads, DialogStatus, FormStatus) and the ng-events only the dialog listened for. NG_CUSTOM_EVENTS keeps the seven the legacy screens still report. The UVE store no longer fetches the edit-content side-panel flag: nothing reads it. The approved editor case that checked $sidePanelEnabled was gone now asserts only the absence of the dialog and its member; the flag read is gone with its name, and the reload effect's comment no longer mentions the dialog reset. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The diff-scoped strict gate checks the lines this layer adds against the repo's strict settings. withPageApi's pageLoad, pageReload and editorSave reach the UVEStore type through an index signature, so the editor and the shell call them through a typed #pageApi view (WithPageApiMethods), which also types language_id as a string. URL_MAP_FOR_CONTENT is read by index, an absent page URL compares as empty, and getTargetUrl takes the URL map as possibly undefined, which it already handled. In the specs, fixture checks narrow by throwing, the iframe window is read through a helper with a single postMessage signature so its spy can be mocked, and two fixtures that model data the server can omit say so with a cast. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#38303) The translate screen is the legacy editor's create form pre-filled from the page, so it needs what the legacy editor panel already handles and the generic legacy-screen panel does not: the workflow wizard and its answer, compare, and the unsaved-changes prompt. The cases now pin that design: translate opens the legacy editor panel with a create request for the page's type in the new language and the translate screen as its create screen; a save loads the translation at its URL and in its language and keeps the panel open; the editor's own redirect after a first save closes the panel and loads that page; closing reloads the page only after a save; and the generic legacy-screen panel is no longer used for it. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he wizard, compare and the dirty prompt (#38303) The translate screen is the legacy editor's create form pre-filled from the page. In the generic legacy-screen panel its workflow-wizard, compare and unsaved-changes events reached nothing, so a workflow action that asks for input on a translation could hang. The legacy editor panel already handles all three, plus DotIframeService and its window hook. translatePage now opens that panel directly through DotSidePanelService, not through the opener, which would ask the server for the type's create screen: the create screen here is the translate URL. A save loads the translation at its URL and in its language and keeps the panel open; the editor's redirect after a first save closes it and loads that page; closing reloads the page only when something was saved, so cancelling a translation changes nothing. The generic panel keeps only the content selector and the menu reorder screen, so its save-page, language-is-changed and close branches and the two NG_CUSTOM_EVENTS members only they used are gone. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…page context (#38303) withPageContext takes the language the request falls back to as a required field. The translate request already names its language, so the page context now names the same one; the strict gate caught the missing field. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntent (#38303) The Relationship field and both block editors decide for themselves how to open other content today: the field reads the side-panel flag and opens its own panel or the centered dialog, and the block editors prompt, write a "return to" breadcrumb and navigate the parent window. Only the form's host knows whether it is a full-screen route or a panel, so these cases move that decision to it. The hosts gain openContent: the full-screen host always opens a panel through the opener; a panel's host replaces its content in place for a related item a Relationship field names, and stacks a panel for anything else, with its own injector so a re-render of the form underneath never closes it. The field asks its host for an existing item and for "create new", reads no flag, never injects the side-panel service, and treats a create's saves as an upsert: the first adds the row, later ones refresh it (FR-030). The block editor field turns either editor's contentletEdit into an edit request from a block editor, and the new editor's toolbar only emits it. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…umb origin (#38303) Inside a panel the host cannot know which content it is showing when it builds the in-place breadcrumb; for an unsaved translation only the field knows it is the source version. The event gains an optional from: { inode; title } the field computes; absent means a fresh trail. Refs #38303 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…38303) A field inside the editor cannot know whether it sits on the full-screen route or inside a panel, so it should not decide whether other content opens in a new panel, a stacked one or in place. EditContentHost gains openContent: the router host always opens a panel through the opener with the shell's injector; the overlay host replaces its own content in place for a Relationship edit (from the origin the field names, or on a fresh trail) and stacks everything else with the panel's injector, so a re-render of the form underneath never closes the panel on top. DotOpenContentEvent gains from, the field-computed breadcrumb origin: inside a panel only the field knows what it shows, and for an unsaved translation that is the source version. The overlay spec's in-place case now names its origin, and a case pins the fresh trail when there is none. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…create related content (#38303) The field used to decide for itself between its own side panel, the centered dialog and in-place navigation, reading the side-panel flag to choose. It cannot know where it is presented, and a panel it opens closes with it on any re-render of the form. It now hands both actions to the host through openContent: an edit names the breadcrumb origin it computes (the current content, or the version an unsaved translation came from), and a create replies to every save of the panel the host opens with an upsert, adding the row on the first save and refreshing it on later ones, so the list never shows the same content twice (FR-030). The field's panel code, the dynamic import of the centered dialog and the store's withFlags slice go. The wrapper spec's create-new cases asserted the centered dialog and now assert the host request and the first-save add. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts to its host (#38303) Editing a contentlet embedded in a block editor navigated the whole window to that content's editor, throwing the author out of the content they were editing. Both editors now only emit contentletEdit; the field binds it on each and asks the editor's host to open the contentlet, which stacks a panel over the current content so nothing is lost. The field now requires the host, as the Relationship field does, so the older specs that mount it provide one. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… contentletEdit (#38303) The toolbar answered "edit contentlet" by prompting about unsaved changes, writing a "return to" breadcrumb to localStorage and navigating the parent window to the content's editor, which threw the author out of the content they were editing. The editor's host now decides where the contentlet opens (a panel stacked over the current content), so the toolbar only emits. ContentletEditUrlService and the breadcrumb utility existed only for that navigation and are deleted, with their providers. The editor's ConfirmationService and its confirm dialog served only that prompt and go too; leaving the dialog without its provider would have bound it to an ancestor's service and shown that service's confirms twice. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and the editor forwards it (#38303) Editing an embedded contentlet from the legacy block editor asked a native confirm, looked up the content type only to pick a URL, wrote a "return to" breadcrumb to localStorage and navigated the parent window, throwing the author out of the content they were editing. The bubble menu now emits contentletEdit with the contentlet's inode and content type, and DotBlockEditorComponent re-emits it, so the Edit Content field and the custom element's bridge can hand it to whoever opens content. The URL helpers that only served that navigation go. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… legacy editor's panel (#38303) Inside the legacy JSP editor both block editors are custom elements, and their edit contentlet used to navigate the parent window. They now only emit contentletEdit, so the bundle forwards it as the edit-contentlet ng-event the legacy editor panel already maps to openContent, and the contentlet opens in a stacked panel. The listener sits on document in the capture phase: the JSP creates the elements, so there is no instance to subscribe to, and Angular Elements dispatches outputs as non-bubbling CustomEvents on the element, which only a capturing listener above it sees. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Relationship field was its last user: every surface now opens content through the opener, in the side panel. The dialog component, its template, styles and spec go, with its export from the library's public API. The comments that named it as a host now describe the hosts that exist: the full-screen route and the side panel. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the side panel (#38303) The last admin surfaces still route content to the legacy iframe dialogs or to a new tab: the legacy JSP portlets' events swap handlers on the global editor flag, the content deep links mount DotContentletsComponent, the Pages portlet's Edit routes to the legacy screen, and the Publishing Queue's dialogs window.open a resolved URL. These tests describe the opener taking all of them, before any code changes. - The custom-event handler opens the four content events through the opener, with the injector the admin root registers, so its panels find the admin's MessageService and toast. It names the portlet that asked, reloads that portlet on save and close, and never reads the flag. - MainComponentLegacyComponent registers its own injector with the handler. - A new deep-link component opens the panel on init. It returns to the parent portlet when the panel closes, and closes the panel without navigating back when the route is left. - The Pages Edit action opens through the opener and refreshes the list through a callback handed to getItems. - Both Publishing Queue dialogs open contentlet rows with their own injector and reload their asset list on save and close. They resolve no URLs, and the shell opens both dialogs without a focus trap. - Stacking appends a second panel and leaves the first unchanged. A stacked editor keeps its own form-bridge frame when the panel underneath closes first. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#38303) The custom-event handler, the deep links, the Pages portlet and the Publishing Queue all send portletId, which the legacy editor receives as angularCurrentPortlet; the builders table listed it only for Content Drive and the UVE. Deep-link creates also carry folderInode from the folder query param. Refs #38303 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the panel under it closes first (#38303) Custom fields reach the form through one global bridge kept on a LIFO stack, and each editor panel pushes a frame when it opens and pops one when it closes. With side panels stacking, the panel underneath can close first. Its pop then took the top frame, the stacked panel's, and left that panel's custom fields on a destroyed bridge while the closed panel's frame stayed active. pushFormBridge now returns the frame, and popFormBridge(frame) removes exactly that frame. For a frame lower down, only the closed context's bridge is destroyed, and the frame above inherits what it had saved, so the stacked panel restores the right bridge when it closes in turn. Called with no frame it still pops the top. The side panel keeps its frame and hands it back on destroy. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nel (#38303) Every legacy JSP portlet (content search, Site Browser, Sites, Calendar, the publishing list) asks for content through the custom-event handler. The handler routed those events to the legacy iframe dialog or to the full-screen editor, picked by a global flag read asynchronously at startup. edit-contentlet, edit-host, create-contentlet and create-host now go through the opener, which picks the editor per content type. The request names the portlet that asked, and every save and the close reload that portlet's list. The JSP's create URL is ignored because the opener resolves the screen itself. The flag swap, the legacy and new-editor routes, and create-contentlet-from-edit-page go; the UVE has handled that event itself since layer 3. The panels live under MainComponentLegacyComponent, which registers its injector with the handler, so they find the admin root's MessageService and toast. The app lazy-loads @dotcms/edit-content with its routes, so a small shared helper imports the opener on demand instead of statically. It opens nothing when the caller's injector was destroyed while the library loaded. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rtlet (#38303) /c/:id/:asset, /c/:id/new/:contentType, /pages/:asset and /pages/new/:contentType mounted the legacy iframe editor as a route component, or sent Pages links to the full-screen editor through two guards. A small DotEditContentDeepLinkComponent now opens the panel on init through the opener (an edit for :asset; a create for :contentType, in the folder ?folder names), naming the portlet DotRouterService reports. Every save reloads the portlet. Closing the panel returns to the portlet's URL with its query params kept. Leaving the route closes the panel without bouncing back, and a route left before the opener loads opens nothing. The paths stay, so bookmarks and the menu's redirects keep working. DotPortletDetailComponent keeps its workflow-or-content switch, with the content branch now the deep link. The create routes and both Pages routes render the deep link directly. DotCreateContentletResolver and the DotContentletEditorService re-provision leave the routes and the root providers. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…panel (#38303) Edit in the Pages listing routed to the legacy edit screen, which replaced the listing. It now opens the page in the side panel over the listing, through the opener and in whichever editor the page type uses, with the Pages portlet's injector. The Pages component hands getItems a refresh that reloads the listing on every save and when the panel closes. The refresh refetches the current page of results with the current filters rather than the one row, because the panel may have deleted or archived the page and a row lookup would then report an error. The dead DotPageStore provider goes. The spec's end-to-end edit case asserted the old route and now asserts the panel. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…their dialog (#38303) Clicking a contentlet row in the asset list or the select-bundle dialog opened its editor in a new browser tab, through a URL each dialog resolved for every contentlet on every load. A row now opens the content in the side panel over the dialog, through the opener, with the dialog's injector. Closing the panel returns to the dialog on the same row, and every save and the close reload the dialog's assets (FR-017). Non-contentlet rows stay plain. The URL resolvers, $assetEditUrls, window.open and the grouping util that served them go, replaced by one isEditableContentlet predicate. Both dialogs open without a PrimeNG focus trap, which would keep the panel's editor from taking keystrokes. The project's Vitest config now inlines TipTap's ESM-only packages, as dot-query-tool's does, so specs load the real @dotcms/edit-content and the two dialog specs drop their module mock. The select-bundle spec sets its contentlet rows on the dialog instead of a one-shot getBundleAssets value that the next test's mount consumed. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… compiles (#38303) The create deep-link routes replaced the legacy editor's routes file, which carried a typed redirect for /c/:id/new. Moved into the untyped iframe-portlet route list, its pathMatch widened to string and the app build failed (TS2322). The literal is now narrowed. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#38303) Every admin surface now opens content through the opener, so the iframe wrappers that carried the legacy edit, create, add and reorder screens have no callers. The wrappers are dot-contentlet-wrapper, dot-edit-contentlet, dot-create-contentlet, dot-add-contentlet, dot-reorder-menu, DotContentletEditorService, the create resolver and its routes, plus DotContentletsComponent. Their files were deleted in 41086d6, whose git index already held the removal. This commit removes what still pointed at them: - the always-on <dot-create-contentlet> mount in the admin root, and its onCustomEvent handler; - the component registry entry; - the test bed's and the workflow-task spec's providers; - a stray JSDoc tag; - the admin root spec's case for the removed mount, with its now-unused handler variable. dot-iframe-dialog stays: the workflow task detail still uses it. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…38303) The strict gate flagged six spots on lines this layer added: - The current portlet's id is optional, so the iframe reload in the handler and the deep link now runs only when there is one. - The Pages store's methods come from an index signature. - The Pages refresh handed to getItems is optional. - forceDestroy is private on the bridge, which the spec now spies through a narrowed view. Each is fixed where it is read; nothing else changes. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The listing refresh reached getPages with bracket access only to satisfy the strict gate's noPropertyAccessFromIndexSignature check. The method is a plain store method that takes optional params, and the app's own type-check accepts the direct call. Under strict flags, the store's initial state fails withState (currentUser: null against DotCurrentUser | undefined, store.ts:65), which collapses the whole store type to an index signature. Every access to the store, not only this one, then reads as an index-signature access, so the bracket only hid that. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hod (#38303) Under the strict convention the Pages store's type collapsed to an index signature, so every member read off it, `getPages` included, failed TS4111 in dot-pages.component. Three mismatches caused it: - The state said `currentUser?: DotCurrentUser` while the initial state set `null`. It is now `DotCurrentUser | null`, which is what the store spec already expects. - Both site-switch hooks declared `signalMethod<DotSite>` but are fed `GlobalStore.siteDetails`, a `Signal<DotSite | null>`. They already return early on a missing site, so they now take `DotSite | null`. This mismatch kept `withFavorites` from type-checking. - `withFavorites()` came last. Its required input state became an inference source for every feature before it, so TypeScript stopped inferring those features from their own results and `signalStore` matched no overload. It now sits right after `withState`, where its input depends only on the state. The one visible effect is that on a site switch the favorites request is sent before the pages request. Refs #38303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8e4ea6c to
8338af1
Compare
|
Two commits added at the top of this layer, both housekeeping: a formatting-only |
Spec-Kit PR 2, part 2 of 3. Based on part 1 (PR #38324), so the diff shows only this part. It holds what were stack layers 3, 4 and 5: every editor entry point in the page editor (UVE), fields and block editors asking their host instead of opening panels or navigating, and the last admin surfaces (legacy JSP portlets, deep links, Pages, Publishing Queue) with the admin's legacy iframe wrappers deleted. The implementation was first published as seven stacked PRs; it was folded to three on 2026-10-29. The commit history keeps the layer boundaries, so the Commits tab reads as three reviews in order.
Refs #38303
Review by layer
68c1303df0..d4eef8a38c, 12 commits, Red gates once2793bbd5andbe367367dalibs/portlets/edit-ema/portlet: editor, shell, actions handler, utils; deletion ofdot-ema-dialog, its store andDotEmaWorkflowActionsServiced4eef8a38c..05d6e17b9f, 11 commits, Red gate on9691f02d4cEditContentHost.openContent, the two hosts, Relationship and block editor fields,new-block-editortoolbar,block-editorbubble menu,apps/dotcms-block-editor/src/main.ts; deletion ofdot-create-content-dialog05d6e17b9f..8338af145d, 13 commits, Red gate oncae822ce36apps/dotcms-ui: custom-event handler, deep-link component and routes, Pages,open-edit-content-panel.ts;libs/portlets/dot-publishing-queue;libs/edit-content-bridge; deletion ofdot-contentlet-editor/,dot-contentlets/, the Pages guardsEach layer ends with a
style(...)commit that only runs prettier on files the layer committed unformatted. Review layer 5 as a range, not commit by commit:41086d6499carries staged folder deletions and does not compile on its own (the references go in the next commit); the final tree is what every check below ran on.Layer 3: the page editor (User Stories 1 and 5)
Every editor entry point in the UVE (pencil, full editor, URL-map edit, Edit Source fallback, palette drop, container "+", page Properties, translate, SDK
editContentlet()andcreateContentlet()) sets oneDotEditContentRequestbound throughinjectEditContentPanel. Legacy screens that are not editors (content selector, reorder menu) open in the generic legacy-screen panel; the form selector opens as panel content. The page refreshes on everysavedwhile the panel stays open; a palette create inserts once; URL-mapped saves load the new URL; requests carry the experiment variant and the site.dot-ema-dialog, its store andDotEmaWorkflowActionsServiceare deleted.Decisions the tests encode:
languageChanged(id)callspageLoad({ language_id: '3' })as a string becauseDotPageAssetParams.language_idis a string; a page'ssavedloads the page'sURL_MAP_FOR_CONTENT, else itsurl, when it differs from the current page URL; the cancel event isNG_CUSTOM_EVENTS.CANCEL_SAVING_MENU_ORDER; edit requests takelanguageIdfrom the contentlet, falling back to the page language; create requests carry no title (the opener heads them with the type name).One design revision after the Red gate: translating a page opens the legacy editor panel, not the generic legacy-screen panel, because the generic panel lost the translate wizard, the compare view and the dirty prompt (
be367367da,76de1faa8a,2bd58f9f57).Layer 4: fields and block editors (User Story 4)
EditContentHost.openContent(event)is the one way a field asks for other content. The full-screen host (RouterEditContentHost) always opens a panel throughDotEditContentOpenerServicewith the shell's injector. The panel host (OverlayEditContentHost) replaces its content in place with a breadcrumb for an edit coming from a Relationship field, and stacks a second panel for everything else (any create, a block editor's edit), opened with the panel's injector so a re-render of the form underneath never closes the panel on top.DotOpenContentEvent.from?: { inode; title }: the field names the breadcrumb's origin, because inside a panel only the field knows what it is showing (an unsaved translation names its source version). Absent means a fresh trail. Recorded incontracts/open-content-from-fields.mdanddata-model.md.openRelatedand "create new" both callhost.openContent. The create reply is an upsert by identifier: the first save adds the row, later saves refresh it, so the list never shows the created item twice (FR-030). The field's own side-panel code, the dynamic import of the centered dialog, the flag read and the store'swithFlagsslice are gone. "Create new" stays disabled for a related type on the legacy editor.(contentletEdit)on both editors and hands{ inode, contentType }to the host withsource: 'block-editor'.editContentlet()only emits. Deleted:navigateToContentEditor, the unsaved-changes confirm and the editor's ownConfirmationServiceprovider and<p-confirmdialog>,ContentletEditUrlService,breadcrumb.utils.ts.contentletEdit = output<{ inode; contentType }>();goToContentlet()only emits it. The nativeconfirm, the content type lookup, therelationshipReturnValuelocalStorage write,generateBackUrl,extractInodeFromUrland thewindow.parentnavigation are deleted.DotBlockEditorComponentre-emits it through a new@Output() contentletEdit(this lib keeps its decorator style; it is on its way out).apps/dotcms-block-editor/src/main.ts): a capture-phasecontentletEditlistener ondocument, filtered to the two tags this bundle registers, re-dispatchesng-event { name: 'edit-contentlet', data: { inode, contentType } }. Capture because Angular Elements dispatches outputs as non-bubblingCustomEvents on the element. The legacy editor panel already maps that event toopenContent(layer 1, unit-tested).libs/edit-content/src/lib/components/dot-create-content-dialog/and its export.EditContentDialogData, the layout's#interceptDirtyClose/#dialogRefand the overlay host's optionalDynamicDialogRefstay until part 3, where they go with the Query Tool move.Layer 5: the admin surfaces (User Stories 2b and 6)
DotCustomEventHandlerServiceroutesedit-contentlet,edit-host,create-contentletandcreate-hosttoDotEditContentOpenerService.open. The request names the portlet that was current when the event arrived; every save and the close reload that portlet's list.MainComponentLegacyComponentregisters its own injector with the handler (setInjector) so panels opened from JSP events find the rootMessageServiceand<p-toast>.apps/dotcms-ui/src/app/api/util/open-edit-content-panel.ts).app.routes.tslazy-loads@dotcms/edit-content, so a static import anywhere in the app is forbidden by the Nx module-boundary rule and would pull the library into the main bundle. The helper awaitsimport('@dotcms/edit-content'), skips opening if the caller'sDestroyRefis already destroyed, and resolves the opener from the caller's injector. The main bundle grew by 209 bytes./c/:id/:asset,/c/:id/new/:contentType,/pages/:asset,/pages/new/:contentType):DotEditContentDeepLinkComponentopens the panel on init, reloads the parent portlet on save, returns to the parent portlet URL with query params preserved when the panel closes, and closes the panel without navigating when the route is left.DotPortletDetailComponentkeeps the workflow-task branch; the Pages guards andDotCreateContentletResolverare gone.portletId: 'pages'and the component's injector; save and close refresh the listing with the current filters and page (getPages()with no arguments). The deadDotPageStoreprovider is dropped. The store's state is typed (currentUser: DotCurrentUser | null) sogetPagesis a known method for the strict gate (467460d179).window.open, the URL resolvers and$assetEditUrlsare gone, replaced by oneisEditableContentletpredicate. BothDialogService.openconfigs setfocusTrap: falseso the panel over the modal owns focus.@dotcms/edit-content-bridge):pushFormBridge()returns a frame andpopFormBridge(frame?)removes exactly that frame, so a panel closed under a panel stacked over it no longer pops the stacked panel's bridge.view/components/dot-contentlet-editor/(wrapper, edit, create, add, reorder,DotContentletEditorService, resolver, routes, specs),portlets/dot-porlet-detail/dot-contentlets/, the<dot-create-contentlet>mount andonCustomEventin the admin root, the Pages guards.dot-iframe-dialogstays fordot-workflow-task-detail.Tests (TDD, gates T051, T061, T073, T088 and T098 approved on the Red commits)
core-web/)pnpm nx test portlets-edit-ema-portletpnpm nx test edit-contentpnpm nx test new-block-editorpnpm nx test block-editorskip:test, never runs in CI, and the same cases fail with the layer 2 version of the specpnpm nx test dotcms-uipnpm nx test portlets-dot-publishing-queue-portletpnpm nx test edit-content-bridgepnpm nx affected -t lint --base=origin/38303-sidepanel-everything --exclude=tag:skip:lintpnpm nx format:checkagainst part 1pnpm nx build dotcms-ui,pnpm nx build dotcms-block-editorManual-only exceptions (agreed at T073 and T098)
/dotcms-block-editor/main.jsfrom the backend's webapp, so the bundle has to be rebuilt in the checkout the backend mounts.Checklist
localStorageand stop navigating the parent window; the JSP bridge forwards onlyinodeandcontentTypefrom events whose target is one of the two editor tags; the Publishing Queue stops opening new tabs; the legacy screens load same-origin admin URLs through the generic panel's guard.Additional Info
38303-sidepanel-everything(docs(edit-content): spec for opening every content edit and create in the side panel (#38303) #38305) → feat(edit-content): side-panel opener foundation, and Content Drive opens content through it (#38303) #38324 (layers 1 and 2) → this (fmontes/38303-5-admin-surfaces) → feat(query-tool): the Query Tool opens content in the side panel; flag and dialog-era code removed; e2e specs (#38303) #38345 (layers 6 and 7). The intermediate layer branches still exist and anchor the compare links above; their PRs (feat(uve): open every editor and legacy screen through the side panel (#38303) #38330, feat(edit-content): fields ask their host to open content; block editors stop navigating (#38303) #38332) are closed.variantNameon every request; the backend's by-action fire endpoint does not read it yet, so the first edit of still-DEFAULT content from an experiment page needs a backend follow-up.🤖 Generated with Claude Code