Editor navigation: return to where you came from, select a hidden page's parent in the tree, page actions in the page module #149

Closed
opened 2026-10-06 00:08:17 +02:00 by dominik.polakovics · 0 comments

This was generated by AI during triage.

Summary

Moving between the List module, the page module, page properties and the element edit page loses context. Every Close / Save & close goes to a fixed place. The page tree shows nothing selected for a page it hides. And a hidden page offers no way back to its list and no page actions where you edit it. This issue makes the editor remember which view each view was opened from, keeps the tree pointing at the right place, and puts the page actions on the page itself, so a workflow like List → news item → edit → back behaves as expected.

Current behavior

Found while analysing the news workflow (news items live in a hideChildPages section and are managed in the List module, see #79), reproduced in a browser against harness serve on a site with harness add news, and re-checked against the code during triage:

  • Clicking a title in the List module opens the page module for that page. For a child of a hideChildPages section the tree draws no row, and nothing in the tree is selected, not even the section.
  • The page module of such a page has no way back to its list:
    • "Open list" appears only on the section itself.
    • The parent breadcrumb opens the section's page, not its list.
    • Switching the rail to List shows the page's own (empty) child list: "This section has no child pages yet."
  • Breadcrumbs show path segments including the file name: / news / summer-meetup / _index.md.
  • Page properties (?props=1): Close and Save & close always just drop ?props=1, so they land on the page module of the same page. A List-module create opens properties when the chosen type has fields the create dialog does not show (news-item does: author is not create: true), so after Save & close the author is in the page module, not back in the List; it takes 2–3 presses of Back to return.
  • The element edit page (/edit/<lang>/<page>/<uid>): Close, Save & Close and "Back to page" always go to the page module of the element's page, even when the edit page was opened by clicking an element in the View module's preview.
  • The page tree never expands to reveal the selected page.
  • Page actions (Rename, Enable/Disable, Delete…) exist only in the tree row menu; List rows have Enable/Disable and Delete… but no Rename. A page hidden from the tree therefore cannot be renamed properly: changing its title in properties keeps the old URL slug, because the rename that re-derives the slug (records an alias, rewrites links) is only reachable from the tree.
  • The delete dialog calls every editor-created page a "section", because editor-created pages are branch bundles: "Delete the section “Q2 results”? Everything beneath it is removed permanently: 0 pages inside this section will be deleted with it. This cannot be undone."

Desired behavior

Return to where you came from

  • A return query parameter on editor routes names the view that Close / Save & close / the back button on the current view goes to.
  • Every navigation into a more detailed view writes the full URL it was opened from (path and query) as return. Every close reads it.
  • The chain nests. Opening properties from a page view that itself carries a return stores that page view's URL, including its own return, as properties' return. So every close goes exactly one step back.
  • Writers:
    • a List module row title;
    • a List module create, for the properties view it opens;
    • a View module preview click, for the element edit page;
    • the page module's element pencil, element title and insert follow, for the element edit page (the pencil and the title are real links, so their href carries it too and open-in-new-tab keeps it);
    • the page module's Properties button.
  • Readers:
    • properties Close and Save & close;
    • the element edit page's Close, Save & Close and "Back to page";
    • the page module's back button (below);
    • the navigation after deleting the open page (below).
  • Without a return (old links, typed URLs) behavior is as today, except for the hidden-page fallbacks below.
  • Only a same-origin path that parses as an editor route of a registered module is honored; anything else is ignored. No open redirect. parseRoute skips empty segments, so //page/en/x parses as a page route: a raw value starting with // must be rejected before it is parsed.
  • Tree clicks, breadcrumb clicks and rail switches drop return; they are deliberate navigations elsewhere. Reload, Back and shared links keep it, because it lives in the URL.
  • The unsaved-changes guard keeps intercepting every close; they stay router navigations.

Back button in the page module

  • The page module's docheader shows a back button whenever its URL carries a valid return, and goes there. It is labelled with its target: the title of the page the return route names (e.g. "← News" for that section's List), or that module's label when the route names no page.
  • A hidden page (one inside a hideChildPages section) shows it without a return too. It then goes to the parent section in the List module and is labelled with that section's title. #153 later re-points this fallback to the module that manages the section.
  • Any other page without a return shows no back button. Properties show none either; their Close plays that role.
  • It leads the button row, as in [← News] [Publish] [Properties] [Undo] [View] [⋯].

Orientation for hidden pages

  • When the selected page lies inside a hideChildPages section (at any depth: the tree draws none of its descendants), that section's tree row, the nearest ancestor the tree draws, is shown as the selected row (same selected styling and aria-current). No extra or temporary row is drawn for the hidden page.
  • On every navigation the tree expands the ancestors of the selected row (for a hidden page, of its section) and scrolls that row into view when it lies outside the tree pane's visible area, so the selection is visible.
  • Breadcrumbs, in the page module and in the List module, show page titles instead of path segments, with the home page first in place of today's leading "/": Home / News / Summer meetup. The "/" separator stays (the Media module's folder path shares it and does not change). Ancestor pages, the home page included, are clickable and select that page as today; a synthesized directory stays static.

Page actions

  • One shared set of page actions — Rename, Enable/Disable, Delete… — is used by the tree row menu, the List module rows and a new "⋯" menu in the page module docheader. The tree keeps its extra entries (New subpage, Move up/down). The content root keeps today's protections, so its docheader has no "⋯" menu.
  • The shared definition decides which actions a page offers, their labels and rules, and the operation each runs. Where a surface goes afterwards stays its own:
    • the tree navigates after its actions exactly as today;
    • a List row's actions leave the List on its section;
    • the docheader's follows are described below.
  • Rename edits inline in the tree, as today. In the List rows and the docheader menu it reads "Rename…" and asks for the new title in a small dialog prefilled with the current one. It is the same rename operation as the tree's: slug re-derived, alias recorded, links rewritten.
  • Renaming the open page from its docheader follows it to its new path, replacing the history entry, and keeps its return.
  • Deleting the open page from its own docheader navigates, replacing the history entry, to return; without one, to the parent section's list for a hidden page, else to the parent page.
  • Like every mutation follow today, these navigations skip the unsaved-changes guard and run with route repair suspended.
  • The delete dialog names the page by its type label ("Delete News item “Q2 results”?"); a page whose type has no label keeps today's "Delete “
> *This was generated by AI during triage.* ## Summary Moving between the List module, the page module, page properties and the element edit page loses context. Every Close / Save & close goes to a fixed place. The page tree shows nothing selected for a page it hides. And a hidden page offers no way back to its list and no page actions where you edit it. This issue makes the editor remember which view each view was opened from, keeps the tree pointing at the right place, and puts the page actions on the page itself, so a workflow like List → news item → edit → back behaves as expected. ## Current behavior Found while analysing the news workflow (news items live in a `hideChildPages` section and are managed in the List module, see #79), reproduced in a browser against `harness serve` on a site with `harness add news`, and re-checked against the code during triage: - Clicking a title in the List module opens the page module for that page. For a child of a `hideChildPages` section the tree draws no row, and nothing in the tree is selected, not even the section. - The page module of such a page has no way back to its list: - "Open list" appears only on the section itself. - The parent breadcrumb opens the section's page, not its list. - Switching the rail to List shows the page's own (empty) child list: "This section has no child pages yet." - Breadcrumbs show path segments including the file name: `/ news / summer-meetup / _index.md`. - Page properties (`?props=1`): Close and Save & close always just drop `?props=1`, so they land on the page module of the same page. A List-module create opens properties when the chosen type has fields the create dialog does not show (`news-item` does: `author` is not `create: true`), so after Save & close the author is in the page module, not back in the List; it takes 2–3 presses of Back to return. - The element edit page (`/edit/<lang>/<page>/<uid>`): Close, Save & Close and "Back to page" always go to the page module of the element's page, even when the edit page was opened by clicking an element in the View module's preview. - The page tree never expands to reveal the selected page. - Page actions (Rename, Enable/Disable, Delete…) exist only in the tree row menu; List rows have Enable/Disable and Delete… but no Rename. A page hidden from the tree therefore cannot be renamed properly: changing its title in properties keeps the old URL slug, because the rename that re-derives the slug (records an alias, rewrites links) is only reachable from the tree. - The delete dialog calls every editor-created page a "section", because editor-created pages are branch bundles: "Delete the section “Q2 results”? Everything beneath it is removed permanently: 0 pages inside this section will be deleted with it. This cannot be undone." ## Desired behavior ### Return to where you came from - A `return` query parameter on editor routes names the view that Close / Save & close / the back button on the current view goes to. - Every navigation into a more detailed view writes the full URL it was opened from (path and query) as `return`. Every close reads it. - The chain nests. Opening properties from a page view that itself carries a `return` stores that page view's URL, including its own `return`, as properties' `return`. So every close goes exactly one step back. - Writers: - a List module row title; - a List module create, for the properties view it opens; - a View module preview click, for the element edit page; - the page module's element pencil, element title and insert follow, for the element edit page (the pencil and the title are real links, so their `href` carries it too and open-in-new-tab keeps it); - the page module's Properties button. - Readers: - properties Close and Save & close; - the element edit page's Close, Save & Close and "Back to page"; - the page module's back button (below); - the navigation after deleting the open page (below). - Without a `return` (old links, typed URLs) behavior is as today, except for the hidden-page fallbacks below. - Only a same-origin path that parses as an editor route of a registered module is honored; anything else is ignored. No open redirect. `parseRoute` skips empty segments, so `//page/en/x` parses as a page route: a raw value starting with `//` must be rejected before it is parsed. - Tree clicks, breadcrumb clicks and rail switches drop `return`; they are deliberate navigations elsewhere. Reload, Back and shared links keep it, because it lives in the URL. - The unsaved-changes guard keeps intercepting every close; they stay router navigations. ### Back button in the page module - The page module's docheader shows a back button whenever its URL carries a valid `return`, and goes there. It is labelled with its target: the title of the page the `return` route names (e.g. "← News" for that section's List), or that module's label when the route names no page. - A hidden page (one inside a `hideChildPages` section) shows it without a `return` too. It then goes to the parent section in the List module and is labelled with that section's title. #153 later re-points this fallback to the module that manages the section. - Any other page without a `return` shows no back button. Properties show none either; their Close plays that role. - It leads the button row, as in `[← News] [Publish] [Properties] [Undo] [View] [⋯]`. ### Orientation for hidden pages - When the selected page lies inside a `hideChildPages` section (at any depth: the tree draws none of its descendants), that section's tree row, the nearest ancestor the tree draws, is shown as the selected row (same selected styling and `aria-current`). No extra or temporary row is drawn for the hidden page. - On every navigation the tree expands the ancestors of the selected row (for a hidden page, of its section) and scrolls that row into view when it lies outside the tree pane's visible area, so the selection is visible. - Breadcrumbs, in the page module and in the List module, show page titles instead of path segments, with the home page first in place of today's leading "/": `Home / News / Summer meetup`. The "/" separator stays (the Media module's folder path shares it and does not change). Ancestor pages, the home page included, are clickable and select that page as today; a synthesized directory stays static. ### Page actions - One shared set of page actions — Rename, Enable/Disable, Delete… — is used by the tree row menu, the List module rows and a new "⋯" menu in the page module docheader. The tree keeps its extra entries (New subpage, Move up/down). The content root keeps today's protections, so its docheader has no "⋯" menu. - The shared definition decides which actions a page offers, their labels and rules, and the operation each runs. Where a surface goes afterwards stays its own: - the tree navigates after its actions exactly as today; - a List row's actions leave the List on its section; - the docheader's follows are described below. - Rename edits inline in the tree, as today. In the List rows and the docheader menu it reads "Rename…" and asks for the new title in a small dialog prefilled with the current one. It is the same rename operation as the tree's: slug re-derived, alias recorded, links rewritten. - Renaming the open page from its docheader follows it to its new path, replacing the history entry, and keeps its `return`. - Deleting the open page from its own docheader navigates, replacing the history entry, to `return`; without one, to the parent section's list for a hidden page, else to the parent page. - Like every mutation follow today, these navigations skip the unsaved-changes guard and run with route repair suspended. - The delete dialog names the page by its type label ("Delete News item “Q2 results”?"); a page whose type has no label keeps today's "Delete “<title>”?". It warns about descendants only when the page actually has any. ## Decisions - The origin lives in a URL parameter, not in in-memory history. The editor's rule is that the URL is the source of truth (see `web/src/routing.ts`), so reload, Back and shared links must reconstruct the same view. TYPO3's `returnUrl` is the same model. - For a hidden page, its parent section is selected in the tree, with no temporary row. This is the maintainer's choice, and it mirrors TYPO3, where the storage folder stays selected while you edit a record stored in it. - The back button appears on any page view with a `return`, not only on hidden pages, and is labelled with where it goes. The maintainer chose this in triage over a plain Close shown only with a `return`. Every List row title writes a `return`, and the List can list any section's children (Home's top-level pages too), so a visible page opened from the List would otherwise carry a `return` nothing reads. - A hidden page's back button falls back to its parent's List, because that is where hidden pages are managed. - A List row's actions never move the List off its section (maintainer, in triage). The tree's rename follow selects the renamed page within the current module (`selectPage`), so reused as-is in the List it would jump to `/list/<lang>/<item>`, the item's empty child list. - Breadcrumbs change in both the page module and the List, and the separator stays "/" (maintainer, in triage). The List's crumbs share `pathCrumbs` so that both read identically, and the separator component is shared with the Media module. - The tree reveal also scrolls the selected row into view (maintainer, in triage), so "visible" holds on a tree taller than its pane. - New pages keep starting disabled (`draft: true`). The maintainer confirmed this; it is not changed here. - One issue, one pull request. The back button, the delete follow and the rename follow all read `return`, every part touches the same components, and #153 needs all of it. - No `scaffold/site/AGENTS.md` or README change is needed: this changes editor navigation only, and AGENTS.md's `hideChildPages` and `create` paragraphs stay true. ## Key interfaces - `parseRoute` / `buildRoute` (`web/src/routing.ts`): a helper that reads, validates and builds `return` belongs beside them, so no component spells the parameter itself. - `createRouteSelection` (`web/src/selection.ts`) and the shell's `selectModule` (`web/src/App.tsx`): tree and rail navigations build parameter-less routes; keep it that way. - `handleBridgeSelect` in the shell (`web/src/App.tsx`): the preview click → element edit navigation. - `EditPane` (`web/src/components/EditPane.tsx`): its close target. - `PagePane` (`web/src/components/PagePane.tsx`): props-mode Close / Save & close, docheader buttons, the list-section affordance, breadcrumbs, and the element title and pencil links. - `createPageEditor` (`web/src/stores/pageEditor.ts`): the insert follow to the new element's edit page. - `ListModule` (`web/src/components/ListModule.tsx`): row title navigation, the post-create follow, row actions. Its `typeLabel` already resolves a node's type label, which the delete dialog now needs. - `TreePane` / `TreeRow` (`web/src/components/TreePane.tsx`) and `createTreeController` (`web/src/stores/treeController.ts`): selected-row highlight, expansion, the row menu's actions, and the rename and delete follows. - `createMutations` (`web/src/stores/mutations.ts`) and `followMutation` (`web/src/routerepair.ts`): mutation follows run unguarded with route repair suspended, but can only `selectPage`, which builds a parameter-less route in the current module. Following to a `return` URL after a delete, or keeping `return` across a rename, needs a follow that navigates to a full URL. - `pathCrumbs` / `ancestorChain` (`web/src/pagemanage.ts`): the List's crumb builder. `PagePane` keeps private copies of both; both modules' crumbs should come from one builder. - `DeleteDialog` (`web/src/components/DeleteDialog.tsx`): copy. - Route repair (`web/src/routerepair.ts`): a repaired route must not keep a `return` that leads somewhere the site no longer has. Checked in triage: `repairRoute` rebuilds every repaired route with `buildRoute`, which writes no query, so it already keeps neither `return` nor `props`. Keep it that way and cover it with a test. - `setSearchParams` (`@solidjs/router` 0.16.1), checked in triage: it merges (sets or deletes only the keys it is given and keeps the rest) and pushes a history entry. Opening properties with `setSearchParams({ props: "1" })` alone would leave the page view's `return` in place instead of nesting it, so set `return` explicitly; props Close must navigate to `return`, not just drop `props`. - Scrolling: tests run under jsdom, which has no `scrollIntoView`. `revealFocused` in `web/src/components/HostNav.tsx` already guards for that; follow it. ## Acceptance criteria - [ ] List → item title → Properties → Save & close returns to the item's page module, whose back button ("← News") then returns to the List. - [ ] List → New → (properties opens) → Save & close returns to the List; so does Close. - [ ] List → item → element edit page → Save & Close returns to the item's page module, and its back button still leads to the List. - [ ] View module → click an element in the preview → Save & Close returns to the View module on the same page. - [ ] A `return` naming another origin, a protocol-relative URL (`//host/…`, and `//page/en/x`, which `parseRoute` alone accepts) or an unknown module is ignored, and the old close behavior applies. - [ ] Reloading any of the views above keeps its close target. - [ ] A page opened from the List shows the back button labelled with the List section's title. A hidden page reached without a `return` (preview, link, typed URL) shows one leading to its parent section in the List module. A visible page reached from the tree shows none, and neither do properties. - [ ] With a page inside a `hideChildPages` section selected, the section's tree row is selected (styling and `aria-current`) and visible, whatever the expansion state was before; no row is drawn for the hidden page. - [ ] Selecting a page inside collapsed sections expands the tree to it, and scrolls its row into view when it lies outside the tree pane's visible area. - [ ] Breadcrumbs in the page module and the List show titles, home first (`Home / News / Summer meetup`); ancestor pages, the home page included, are clickable. The Media module's folder path is unchanged. - [ ] The page module's "⋯" menu offers Rename…, Enable/Disable and Delete… under the same rules as the tree, and is absent on the content root. Renaming a hidden page from it re-derives its slug and records an alias; the page follows to its new path and keeps its `return`. - [ ] List rows offer Rename… beside Enable/Disable and Delete…; after any of them the List stays on its section. - [ ] Deleting the open page from its own page lands on `return`, else on the parent's list for a hidden page, else on the parent page; Back does not return to the deleted page's URL. - [ ] The delete dialog for a childless page reads "Delete <type label> “<title>”?" with no "0 pages" sentence; a page with descendants still gets the count warning; a page whose type has no label reads "Delete “<title>”?". - [ ] Tree, List rows and docheader take their page actions from one shared definition. - [ ] A repaired route carries no query parameters (neither `return` nor `props`). ## How to verify - `cd web && npm test`: tests for the `return` helper (parse, validate, build), for each writer and reader above, for the back button's three cases, for the tree's selected-parent and reveal behavior, for the crumbs, and for the shared action set. The suite was green at triage (56 files, 1578 tests). - `make test` stays green; no server change is expected. It builds `web/dist` first: a bare `go test ./...` in a fresh worktree fails at setup in `cmd/harness` and `web`, because `web/embed.go` embeds `web/dist`. - In a browser against `harness serve` on a site with `harness add news` (give the site a git remote so publishing works), walk the acceptance flows. The analysis behind this issue used Playwright with the system Chromium. ## Assumptions to check - None open. Both assumptions in the first draft (what route repair does with the query, and whether `setSearchParams` merges) were checked in triage; the answers are under Key interfaces. ## Out of scope - Replacing the List module with editor modules declared by feature bundles (a News module): that is #153. Until then the List module stays the home of hidden pages, so the List-related parts above apply to it as it is today. Don't extend the List beyond them. - Where the tree's own actions land afterwards: a tree rename or delete navigates exactly as today. - Changing that new pages start disabled. - Moving a hidden page to another section (there is no UI for it today). - Publishing pending deletions from the List. ## Blocked by None - can start immediately
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle#149
No description provided.