Skip to content

fix(edit-content): refresh History, Comments and Reference Pages after save #36617 - #36901

Draft
adrianjm-dotCMS wants to merge 4 commits into
mainfrom
adrianjm-dotCMS/history-comments-side-panel-not-refreshing-when
Draft

fix(edit-content): refresh History, Comments and Reference Pages after save #36617#36901
adrianjm-dotCMS wants to merge 4 commits into
mainfrom
adrianjm-dotCMS/history-comments-side-panel-not-refreshing-when

Conversation

@adrianjm-dotCMS

@adrianjm-dotCMS adrianjm-dotCMS commented Aug 5, 2026

Copy link
Copy Markdown
Member

Fixes #36617

Proposed Changes

The sidebar's History, Comments and Reference Pages stayed stale after a save/publish and needed a manual page reload. There were two independent root causes.

  • The refresh effects lived on a component that gets destroyed. They sat on DotEditContentSidebarComponent, which the layout's @if destroys and recreates — taking the effects with it. Moved into store-level withHooks({ onInit }) in withActivities and withInformation, so they live as long as the store does. This matches the existing lock / workflow / history features.

  • withHistory did not recognise a newly minted version. Its memo only invalidated on ${identifier}:${languageId}, but a save mints a new inode under the same identifier and locale, so loadVersions never re-fired. The symptom differed per host, which is why it looked like two separate bugs:

Screen.Recording.2026-08-05.at.3.47.52.PM.mov
Full-screen Dialog
Navigates on save? Yes → runs initializeExistingContent No
Effect on the list Emptied (versions: []) Left untouched
What you saw "it went blank" "it didn't update"

Added two invalidation signals: a cleared list (status === INIT, ignored mid-reload so it cannot fetch the identifier being left behind) and a live-inode baseline that only advances when not viewing a historical version, so browsing versions — and returning from them — does not refetch.

  • Fixed signal granularity in both effects. They read store.uiState(), i.e. the whole slice. Every writer replaces that object wholesale, so the effects refetched on unrelated UI changes — including the view flip loadVersions performs internally, costing 3–4 redundant requests per save. Now they read the store.uiState.isSidebarOpen() leaf.

Checklist

  • Tests
  • Translations
  • Security Implications Contemplated (none — no new endpoints, inputs or permissions; only client-side refresh timing)

Additional Info

Tests: 2180 passing across 111 suites in edit-content; lint and typecheck clean. New coverage includes publish-in-dialog, publish-while-comparing, the mid-reload case, historical/compare round trips, and a regression test for the signal-granularity fix (verified it fails against the previous code).

One existing test changed expectationshould not reload anything when only the version inode changes asserted that moving only the inode does not reload, which was literally the bug. Rewritten as should reload versions but not push publish history when the live inode moves. This is safe because browsing versions always goes through loadVersionContent, which sets isViewingHistoricalVersion; a bare inode move only happens on save/publish. The browsing cases are now covered by dedicated tests that set the realistic state.

Known follow-ups, deliberately out of scope:

  • Push Publish History still does not reload on publish (kept the original per-identifier scoping).
  • clearVersions() / resetVersions() / clearPushPublishHistory() / resetPushPublishHistory() in history.feature.ts have no production call sites — dead code worth removing separately.

🤖 Generated with Claude Code

…r save #36617

The sidebar's History, Comments and Reference Pages stayed stale after a
save/publish, requiring a manual page reload. Two root causes:

- The refresh effects lived on DotEditContentSidebarComponent, which the
  layout's @if destroys and recreates, taking the effects with it. Moved
  them into store-level withHooks({onInit}) in withActivities and
  withInformation, matching the existing lock/workflow/history features.

- withHistory only invalidated its memo on `${identifier}:${languageId}`,
  but a save mints a new inode under the SAME identifier and locale, so
  loadVersions never re-fired. Full-screen rendered an empty list
  (initializeExistingContent had already cleared it); the dialog host,
  which never navigates, rendered a stale one. Added two invalidation
  signals: a cleared list (status INIT, ignored mid-reload so it cannot
  fetch the identifier being left behind) and a live-inode baseline that
  only advances when not viewing a historical version, so browsing
  versions and returning from them does not refetch.

Both effects now read the isSidebarOpen leaf instead of uiState(): every
writer replaces that slice wholesale, so depending on the object refetched
on unrelated UI changes, including the view flip loadVersions performs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Claude finished @adrianjm-dotCMS's task in 6m 44s —— View job


Code Review

  • Fetch PR diff
  • Review history.feature.ts (core logic change)
  • Review activities/information features (hook moves)
  • Review content.feature.ts + sidebar component
  • Assess test coverage
  • Post review

I traced all three root causes against the store and reload flow. The reasoning holds up and the invalidation logic is carefully guarded. Findings below.

New Issues

None blocking.

Observations (non-blocking)

  • 🟡 Medium: history.feature.ts:791-796 — The new versionsCleared / pushPublishCleared guards fire loadVersions / loadPushPublishHistory whenever those statuses read INIT and state is not LOADING. This is safe only because the loaders exclusively transition INIT → LOADING → LOADED/ERROR and never write INIT back (verified at lines 366/416/425 and 459/507/516). If a future change makes a loader reset to INIT on completion or error, this effect would re-fire in a loop. Worth a one-line inline note pinning that invariant, or an explicit "loading in flight" check, so the coupling isn't silently broken later. Assumption: the only INIT writers are initializeExistingContent and the (dead) clearVersions/clearPushPublishHistory. What to verify: no loader path resets status to INIT.

  • 🟡 Medium: history.feature.ts:781-862 — The effect now reads four signals (contentlet, state, versionsStatus, pushPublishHistoryStatus, isViewingHistoricalVersion) as dependencies. Every state LOADING→LOADED flip re-runs the whole effect. That's intended and the guards suppress redundant fetches, but it's worth confirming there's no path where state toggles repeatedly while versionsStatus === INIT (e.g. a retry that re-enters LOADING without a fetch), which would defer the cleared-list fetch indefinitely. The existing "in-flight" test covers the mid-reload case; a retry/re-LOADING case isn't covered.

Verified correct

  • Store-level withHooks({ onInit }) move (activities.feature.ts:141-166, information.feature.ts:85-110) — effects now outlive the @if-destroyed DotEditContentSidebarComponent; sidebar component correctly drops the effect/untracked imports and the private field (dot-edit-content-sidebar.component.ts, no dangling references).
  • store.uiState.isSidebarOpen() leaf read — valid deep-signal access; isSidebarOpen is a computed off uiState (ui.feature.ts:37). Reading the leaf instead of uiState() correctly avoids refetching on unrelated slice rewrites (including the view flip inside loadVersions).
  • newLiveVersion guarding — gated on loadedLiveInode !== null (no double-fetch on initial load) and !isViewingHistoricalVersion; baseline only advances when not viewing history, so browsing a version and returning does not refetch. Compare view keeps contentlet live, so publish-while-comparing correctly refetches — covered by the new test.
  • Mid-reload suppression — during initializeExistingContent, state === LOADING with the outgoing contentlet still on screen; all guards evaluate false on that pass, so the effect never fetches the identifier being left behind. It fires on the same pass that patches contentlet + state: LOADED while statuses are still INIT. Confirmed against content.feature.ts:275-320 / 382-391.
  • identityChanged && !isInitialLoad narrowing of the compare/historical reset — same-identity refetch (dialog publish) now correctly preserves an active compare session; only a real identity change discards it.
  • Test coverage — the changed expectation (should reload versions but not push publish history when the live inode moves) correctly encodes the fixed behavior, and new tests cover publish-in-compare, in-place-empty reload for both lists, and the in-flight case. The dead-code note (clearVersions/resetVersions/clearPushPublishHistory/resetPushPublishHistory have no production call sites) is accurate — confirmed no non-spec references.

Nice write-up in the PR body; the per-host symptom table made the two root causes easy to verify.
· adrianjm-dotCMS/history-comments-side-panel-not-refreshing-when

…+ e2e #36617

The feature specs verify the store effects against a synthetic store with no
component mounted, so they cannot catch the regression this fix addresses: the
effects used to live on DotEditContentSidebarComponent, which the layout's @if
destroys and recreates. Moving them back would leave every feature spec green.

Two layers close that gap, both parameterized over the full-screen and dialog
hosts since the bug had a different face in each:

- Integration (dot-edit-content.layout.component.spec.ts): real store mounted in
  the real layout with the sidebar as a MockComponent. Asserts the fetches happen
  while the sidebar is not rendered at all, that they survive the sidebar being
  destroyed and recreated, and that a save minting a new inode refreshes without
  any re-initialization (the dialog path). Verified these fail when the store
  hooks are removed.

- E2E (apps/dotcms-ui-e2e/.../sidebar/history-refresh.spec.ts): publishes and
  comments through the UI and asserts History/Comments update with no reload,
  in full-screen and in the dialog (reached via the relationship field's "New
  Content", which needs no page/template fixture). Verified against the pre-fix
  code: full-screen reproduced the empty list (expected 2, got 0) and the dialog
  reproduced the stale list (expected 2, got 1).

Dialog comments are deliberately not covered — the comment form is hidden for
content opened as 'new', which is the only mode that entry point offers. It
needs the UVE pencil flow and a page fixture; documented in the spec.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`nx format:check` — the CI's format-test goal — flagged this file. The
lint-staged hook ran `nx format:write` on it at commit time but left it
unformatted, so the check only surfaced in the pipeline.

Formatting only; no test logic changed. 2186 tests still pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area : Frontend PR changes Angular/TypeScript frontend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

History & Comments side panel not refreshing when editing via UVE (works via content search)

1 participant