Skip to content

Msm#44

Merged
ravuthu merged 31 commits into
mainfrom
msm
Jun 5, 2026
Merged

Msm#44
ravuthu merged 31 commits into
mainfrom
msm

Conversation

@ravuthu

@ravuthu ravuthu commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

Please always provide the GitHub issue(s) your PR is for, as well as test URLs where your change can be observed (before and after):

Fix #

Test URLs:

App - https://da.live/app/aemsites/da-blog-tools/tools/apps/msm/msm?ref=msm

@aem-code-sync

aem-code-sync Bot commented Apr 24, 2026

Copy link
Copy Markdown

Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch and validate page speed.
In case there are problems, just click a checkbox below to rerun the respective action.

  • Re-run all PSI checks
  • Re-run failed PSI checks
  • Re-sync branch
Commits

@aem-code-sync

aem-code-sync Bot commented Apr 24, 2026

Copy link
Copy Markdown
Page Scores Audits Google
📱 / PERFORMANCE A11Y SEO BEST PRACTICES SI FCP LCP TBT CLS PSI
🖥️ / PERFORMANCE A11Y SEO BEST PRACTICES SI FCP LCP TBT CLS PSI

… a shared core (#46)

* feat(msm-plugin): status-first per-row tree UX rewrite

Replaces the action-first model (dropdown + checkbox grid + advanced expander)
with a hierarchical tree showing two status icons per row and inline actions.

Key changes:
- Satellite list renders as a nested expandable tree with per-row Roll out /
  Sync buttons and overflow menus; mid-tier nodes auto-collapse on load
- Two status icons per row: inheritance state (following/local/out-of-sync)
  and AEM publish state (not rolled out / preview only / live current)
- Rollout confirmation uses toggleable scope chips to include/exclude
  satellites; enabling a chip also enables its ancestors
- Roll out to live always does preview first then live, matching da-live
- Source section (satellite view) resolves the effective source by walking
  the MSM chain when the direct parent has no local copy of the page;
  shows a "via X" note and targets the resolved ancestor for pulls
- Source row has same two-icon treatment and out-of-sync detection via
  Last-Modified timestamp comparison
- Overflow menus replace the old advanced expander; Revert / Resume
  inheritance moved into ... menu to keep row actions to one button
- Success banner with per-site open links replaces the old inline status line
- Removed se-select vendor dependency (no longer used)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(msm-plugin): use DA source Last-Modified for editTime in page status

The AEM admin status API edit.lastModified can diverge from the actual DA
source file timestamp. HEAD the DA source directly so the preview/live
behind check reflects the true last-edit time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(msm-plugin): inheritance-aware page status with lazy node loading

getSatellitePageStatus now accepts an explicit editLastModified rather than
fetching it internally. Callers pass either the satellite's own DA timestamp
(cancelled/local copy) or the effective base's timestamp (inherited), so the
"behind" check correctly reflects whether the effective source has changed
since the satellite was last previewed — not just whether the satellite's
own DA file has changed.

Per-node effective base is resolved by walking _ancestorChain to find the
nearest ancestor with local content, falling back to the root-base timestamp.

Satellite data now loads lazily: top-level nodes load on initial render,
deeper nodes load when their parent is expanded. This avoids fetching data
for collapsed subtrees that may never be viewed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(msm-plugin): include editTime in liveState behind check

liveState was only checked against previewTime, missing the case where
content changed after the last publish (editTime > liveTime). This produced
the impossible previewState=behind + liveState=current combination caused by
rollout writing live.lastModified a few ms after preview.lastModified.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(msm-plugin): unify Sync action across source and satellite rows

Pull latest (source section) and Sync (satellite row) were the same action
from opposite directions but with different UX. Both are now labelled Sync,
open the same confirm step, and offer the same Merge / Override choice.

Get from base (no local copy yet) remains a direct action — it is a distinct
operation (creating the override for the first time, not updating one).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(msm-plugin): add console logging for icon2 publish state

Logs editLM source (own vs effective base) in _loadNodes, and the full
input/computed set in getSatellitePageStatus for debugging icon2 accuracy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(msm-plugin): improve icon2 debug logging

- Log full DA source URL for edit reference (resolveEditSource) so it's clear
  which site's file is being used as the edit timestamp, not just the page path
- Log AEM admin status URL in getSatellitePageStatus
- Use date strings from API responses instead of numeric timestamps
- Refactor _effectiveBaseLM into _resolveEditSource returning {site, lastModified}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(msm-plugin): log editResource in getSatellitePageStatus

Pass the full DA source URL of the edit reference through to
getSatellitePageStatus so the log shows which resource the editLastModified
timestamp came from, not just the satellite page path being checked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(msm-plugin): remove console logging added for icon2 debugging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(msm): Phase 2 app action panel redesign

Redesign the MSM app action panel to align with the Phase 1 plugin
interaction model (satellite tree + status icons + inline confirm rows).
Also add per-satellite publish timestamps and align getSatellitePageStatus
return shape between the app and plugin.

Changes:
- action-panel.js: Full rewrite. Replaces satellite grid + picker + bulk
  table with Phase 1-style sat tree (status icons, timestamps, per-row
  Roll out / Sync / more menu). Bulk mode simplified to action selector +
  satellite filter. Source section for satellite/dual roles.
- action-panel.css: Full rewrite matching Phase 1 CSS patterns
  (sat-list/sat-row/confirm-row/overflow-menu/success-banner).
- api.js: getSatellitePageStatus now returns {previewState, liveState,
  previewDate, liveDate}; executeBulkAction updated to new shape.
- utils.js: getSatellitePageStatus adds previewDate/liveDate to return
  value (additive, backward-compatible).
- column-browser.css: Visual refresh (row height, hover/selection colors
  aligned with Phase 1 design tokens).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(msm): column browser always anchors at sites; URL reflects navigation

- Sites column always visible as column 0 for base/dual role. When a
  specific site is in the URL, it auto-expands as column 1 (keeping the
  sites list accessible). Satellite role keeps its existing single-site
  behavior.
- Sites list now includes all MSM participants (bases AND satellites),
  not just base sites.
- URL params (org/site/path) are kept up to date via replaceState as
  the user browses folders and selects pages, so the current state is
  always bookmarkable and shareable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(msm): deep-link path resolution starts at site root column

After adding the sites list as column 0, _navigateToPath was still
starting its search at column 0 (the sites list) and failing to find
page paths. Pass startColIdx=1 from _initFromSite so path resolution
begins at the site root content column.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(msm): update URL synchronously in nav/check handlers instead of async emitSelection

The async emitSelection path has a seq-mismatch guard that can skip the
URL update. Move _updateUrl calls to the synchronous interaction points:
navigateToFolder (after folder loads, guarded by _suppressEmit) and
toggleCheck/handleCheckChange (on check state change).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* debug: log _updateUrl calls in column-browser

* debug: log navigateToFolder suppressEmit state

* chore(msm): remove debug logging from column-browser

* revert(msm): remove URL param updates — app runs in iframe, replaceState only updates iframe URL not the visible address bar

* fix: remove extra blank line

* feat(msm): folder selection propagates to children; partial selection shows indeterminate

When a folder is checked, drilling into it shows all items checked.
Explicitly unchecking an item inside a checked folder adds it to
_unchecked and shows the parent as indeterminate. Re-checking clears
the exclusion. clearChecksAfterColumn also cleans up orphaned
_unchecked entries when a folder leaves the visible columns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(msm): status icons in browser, site checkbox, action panel subtitle

- column-browser: show icon 1 (sync state: inherited/local copy) and
  icon 2 (publish state: live/preview/behind) next to page names.
  Icon 2 loads lazily per column via getSatellitePageStatus.
- column-browser: sites now have checkboxes (single-select — checking
  a new site clears the previous one). Site selection loads root pages
  via emitSelection like folder selection does.
- column-browser: _childPrefix helper centralises the child key prefix
  for both folder and site items. _isAncestorChecked handles site keys.
- action-panel: panel subtitle now always shows the site name; title
  shows the page name (single) or page count (bulk).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(msm): indeterminate state and icon1 not showing

- _unchecked added to static properties so Lit re-renders on exclusion changes
- indeterminate CSS added for custom appearance:none checkboxes (the dash
  mark needs explicit :indeterminate::after since native rendering is off)
- _loadColumnStatus called from initSiteRoot so satellite-role pages
  also get publish-state icons on the initial column

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(msm): checkbox state propagates up tree to site level

_folderChildState checks the adjacent (expanded) child column and
returns checked/indeterminate/unchecked. _displayChecked and
_displayIndeterminate use this to propagate state upward through all
open levels. Visual-only: the toggle logic (_checked/_unchecked) is
unchanged so clicking a folder still directly checks/unchecks it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(msm): deselecting site/folder clears all descendant selections

toggleCheck now uses _displayChecked (not isItemChecked) as the basis
for toggle direction so keyboard space on a propagated-checked folder
works correctly.

Both toggleCheck and handleCheckChange now clear descendant keys from
_checked when unchecking — the missing piece that meant deselecting a
site/folder left individually-checked descendants still selected.
Checking also clears redundant descendant individual-checks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(msm): ancestor uncheck propagates down; fix stale folder cache

_isAncestorUnchecked: mirrors _isAncestorChecked for _unchecked set.
isItemChecked now returns false when an ancestor folder/site is in
_unchecked, so unchecking a folder deselects everything inside it
(fixes the 're-selects on browse-in' confusion).

_folderCache now stores the full page list without baking in the
_unchecked filter. The filter is applied at read-time so count and
selection stay accurate after any exclusion change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: line length

* feat(msm): hasMore count, remove category mutex, fix icon gate

- column-browser: emits hasMore:true when folders/sites are checked so
  the action panel can show N+ instead of N (one-level-deep limitation).
- action-panel: shows pages.length + '+' when hasMore is set.
- column-browser: _isCheckBlocked always returns false — the category
  mutex (inherited vs local mix) was preventing valid selections and is
  no longer needed with the per-satellite-row action model.
- column-browser: _renderItemIcons uses !isFolder && !isSite instead of
  isActionableItem so pages missing an ext field still get icons.
- column-browser: _loadColumnStatus has try-catch so a failed status
  API call doesn't silently abort the async forEach.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* revert(msm): remove app changes — will be done separately

Removes tools/apps/msm/ and the multi-level inheritance design spec
added on this branch. App redesign will be tackled in a separate PR;
this branch now contains only the msm-plugin UX rewrite.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(msm): restore app files to msm branch state

Reverts the previous deletion — app files should match origin/msm,
not be removed. App redesign work will continue separately on top of that baseline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(msm): success banner links, consolidate resume-inheritance, spinner, rollout-all lazy load

- Rollout success links point to aem.page/aem.live instead of DA editor
  (inherited pages have no local copy in DA)
- resume-inheritance success banner drops link entirely — page state
  after removing a local copy is unpredictable
- Consolidate _resumeInheritance + _revertToBase into single
  _resumeInheritance(siteId); self vs satellite distinguished by
  siteId === this.details.site; eliminates resume-source confirm type
- Show busy spinner in banner slot while any action is in flight
- _openRolloutAllConfirm eagerly loads unloaded subtree nodes before
  computing scope, fixing rollout-all missing sites that were never expanded

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(msm): stop rollout scope at override boundaries; show chips for single target

_inheritedInSubtree now tree-walks and stops descending when a node has
a local copy, so sites behind a broken inheritance chain (e.g. france
under europe-with-override) are no longer included in a global rollout.

Scope chips previously required length > 1; changed to > 0 so a single
inherited target is visible in the confirm dialog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(msm): redesign dialog status icons; use S2 icon set

Redesigns the two per-row icons in the MSM dialog:

- Icon 1 (inheritance): gray, colorless — LinkApplied when following base,
  UnLink when local copy exists. Tooltips show parent site title.
- Icon 2 (status): 4-signal color system — green (live/current), amber
  (preview only), orange (out of sync + live), red (needs action).
  Uses AlertDiamond for red to give shape + color severity encoding.

Row layout changed to [icon1] [name] [icon2] [space] [action] [...].
Three-dot more button replaced with CSS.

Replaces all custom hand-drawn SVGs with S2 icons. Only the id attribute
is added to each S2 file; all other content is untouched.

Adds a 5s publish-lag tolerance to timestamp comparisons to absorb the
spurious "stale" signal caused by publishing bumping lastModified.

Adds docs/msm-timestamp-noise.md documenting the timestamp noise problem,
current mitigation, and what a full platform-level fix would require.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(msm): status logic, caching, and urgent button cleanup

- Fix following-base status: distinguish 'not rolled out' (never published)
  from 'base has changed' (needs rollout) — both red, different tooltips
- Remove urgent styling from sync button; status icon is the sole indicator
- Add cache: no-store to getPageTimestamp and getSatellitePageStatus so
  status always reflects current server state without a DevTools cache flush

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(msm): rework app form + column browser; share core with dialog

App: single path input (/org, /org/site, /org/site/path) with URL deep-linking; flat sites list ordered by inheritance level; tri-state, lazily-eager tree selection; S2 icon set; inheritance rendered as a corner badge on the type icon; status icon only on satellite/mid-tier pages where it's meaningful.

Core: extract shared MSM logic to tools/apps/msm/core (config, status, operations, fetch shim, icons). The app's api.js is now a facade and the dialog's config.js/utils.js are thin adapters over core, removing duplicated MSM behavior. All S2 icons consolidated into core/img.

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

* feat(msm): rebuild action panel as bulk matrix + inheritance breadcrumb

Replace the temporary selection summary with a real action panel driven by
the column-browser selection (now enforced single-site).

Downward (base/dual sites): a pages × satellite-subtree matrix with live
per-cell inheritance + status, expandable nested columns, subtree-cascading
target inclusion, and Roll out / Sync / Cancel / Re-enable actions. Execution
groups in-scope cells by (target, source) so sync/cancel pull from the correct
base at any depth. Pages come from the browser only.

Upward (satellite sites): a read-only inheritance table. Actions live solely
in a base's matrix; the panel header is a clickable inheritance breadcrumb
whose ancestor segments re-select the same pages at that base (via new
column-browser selectPaths) and bring up its matrix.

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

* feat(msm): action result feedback + live-rollout fix

- Success banner lists per-result open links (rollout → published page,
  sync/cancel → editor), capped with "+N more".
- Surface action errors: failed results show their messages, banner turns
  amber with an alert icon.
- Move the busy indicator out of the header into a banner in the result slot,
  with a fixed denominator progress count (n/total).
- Fix roll-out-to-live: executeBulkAction 'publish' now previews then
  publishes, since AEM requires a current preview before going live.

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

* feat(msm): tab dual sites, single action bar, page editor links

- Mid-tier (dual) sites get Satellites | Source tabs (default Satellites);
  base-only and satellite-only sites stay single-view. One action bar scoped
  to the active view, so the Source view regains actions without ambiguity.
- Reorder actions (Live/Preview, Merge/Override, Re-enable/Cancel) and style
  the lead as a filled button, the alternate as a gray outline. Stop adopting
  buttons.css in the panel so sl-button's host class no longer double-borders.
- Page paths link to their DA editor doc when one exists (local copy here, base
  page, or the inherited source); assets and pure-inherited-without-source omit.
- Busy progress denominator is fixed up front instead of growing as cells queue.

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

* feat(msm): link override cells to their satellite editor doc

Matrix cells with a local copy now link to that satellite's DA editor doc
(html only), complementing the page-row link. Extract a shared _editUrl helper.

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

* feat(msm): jump down to a satellite from its column header

Satellite column-header names are now links that re-select the same pages at
that satellite (down the tree), mirroring the breadcrumb's jump up. The target
checkbox is separated from the name so it still toggles inclusion. Rename the
shared hand-off to a direction-neutral `navigate-pages` event.

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

* style(msm): quieter satellite jump links in matrix headers

Satellite column-header names render as plain labels and only reveal the
blue/underline jump affordance on hover, so a wide matrix isn't a wall of links.

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

* feat(msm): per-row remove control to drop a page from the selection

Each matrix / source-table row gets a subtle × revealed on row hover or focus.
It emits deselect-page → column-browser deselectPath, which removes the page
and re-emits the selection so panel and browser stay in sync. Keyboard
accessible and disabled while busy.

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

* fix(msm): restore row borders broken by flex on the page cell

The per-row remove control made the page <th> display:flex, which dropped it
out of the table layout and misaligned row borders. Keep the th a table cell
and move the flex to an inner .page-cell wrapper.

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

* fix(msm): code review fixes — aria, dedup constants, _findNode, table headers

- Add aria-selected to tab buttons (role="tab" without it violates ARIA spec)
- Import PUBLISH_LAG_MS from core/fetch via api facade; remove duplicate
  local constants in action-panel and column-browser
- Fix _findNode to short-circuit with Array.some instead of reduce, which
  iterated all nodes even after a match
- Add id/headers attributes to matrix and upward-table cells for screen
  reader association between data cells and their row/column headers
- Remove vestigial deep-link-consumed event dispatch (no consumer, no intent)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(msm): remove _activePages alias, replace reduce chains with for/await

_activePages was an identity getter for _pages with no case where they'd
differ. Replace the two call sites with _pages directly.

Sequential async chains in _loadAll and _execute used promise-reduce, which
works but obscures intent and makes error handling awkward. Replaced with
for/await loops, consistent with the other sequential loops in the file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(msm): consolidate cleanPath to core, rename buildTree, disable form on load

Move cleanPath(pagePath, ext) to core/fetch.js as a single shared export;
remove duplicate private copies from core/status.js and core/operations.js.
Panel was pre-stripping extensions before calling core functions that already
strip them internally — remove the redundant local cleanPath and pass
page.path directly.

Rename private buildTree -> buildDialogTree in core/config.js to make clear
it returns the dialog-specific {siteId} shape, distinct from the exported
buildDescendantTree which uses {site}.

Disable the toolbar input and Load button while _state === 'loading' to
prevent a race condition from rapid re-submits with different orgs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(msm): remove dead executor scoping + unused config exports

executeBulkAction's scope/overrides/onSkipped path was never exercised —
the sole caller pre-groups work and passes none of them. Drop those
params plus checkPageOverrides (which only fed the removed overrides
path). Also remove fully-unused config helpers expandSatellitesWithSubtree,
getDescendantCount, getSubtreeSites and their dead api.js re-exports.

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

* refactor(msm): extract action-panel pure logic to action-panel.model.js

Pull the matrix/source decision logic out of the LitElement component into a
pure module: subtree/selection cascade, effective-source resolution, scope
grouping, and category derivation (previously inlined in _loadRows). The
component now delegates; render code is unchanged. Makes the logic unit-testable
without a DOM or fetch.

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

* test(msm): add unit tests for pure MSM logic + node:test infra

Add node:test/node:assert suites (49 tests, zero new deps) for the extracted
action-panel.model logic and the pure core helpers (config inheritance graph +
getStatusConfig). Tests live under a top-level test/ mirroring the source tree.
Wire up npm test/test:watch scripts, an eslint override for test/**, and a
test step in the Build CI workflow.

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

* perf(msm): incremental action-panel loading instead of full reload per selection

Reworks the action panel's data loading so changing the selection no longer
wipes and refetches everything:

- _resetForSelection now keys context on org|site (a context change is always a
  full reset, since selection is single-site) and treats path changes as an
  incremental delta. Adding pages loads only the new ones; removing pages keeps
  their loaded data (instant re-add, no refetch); include/expand/tab state is
  preserved across selection changes.
- _loadAll/_loadRows/_loadCellsFor take an optional page subset and track
  loaded paths, which also dedups in-flight loads during a subtree crawl (so a
  crawl loads each page once instead of triggering N full reloads).
- Post-action recompute is scoped to the acted pages (reloaded across the whole
  column tree, depth-ordered, so descendants whose effective source shifted are
  recomputed too).

The load decision is extracted into a pure planSelectionLoad() in the model and
unit-tested (noop / reset / incremental, add / remove / re-add, dual-site,
absent role).

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

* refactor(msm): reterm to link/detach + source/linked vocabulary

Replace the misleading classic-AEM vocabulary across the app, plugin, and
shared core. da MSM resolves inheritance at preview/publish time and breaks it
whole-page via a file's existence, so the link metaphor fits better than
"inheritance/rollout".

- Sites: base/satellite -> source/linked (asSource/asLinked, getLinked*, etc.)
- Page states: override/inheriting -> linked/detached (isDetached, categories)
- Actions: rollout -> Publish/Preview, sync override -> Replace,
  cancel/resume inheritance -> Detach/Reconnect (ops + exec tokens)
- Status tips: "Not published", "Source changed - publish needed",
  "Behind source - ..."; status token not-rolled-out -> not-published
- Confirms name the destination ("Publish N pages to M linked sites: ...")
- Internal symbols renamed to match (one pass, no future internal reterm)
- core/config.js reads BOTH base/satellite and source/linked columns, so the
  config sheet can migrate to the new names with no code change
- Tests + both READMEs updated; npm test (58) green, lint clean

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

* fix(msm): refresh browser caches after a content mutation

After the action panel detaches/reconnects/syncs a page, the column browser
kept showing stale link badges and status icons until a manual reload.

- Panel emits `content-changed`; the shell routes it to the browser's new
  refreshContent(), which wires the previously-dead invalidateMergedCache(),
  clears _rowStatus, and re-lists every open column (selection/path preserved)
- Call clearMsmCache() on Load so an edited MSM config sheet is picked up
- Add cache:'no-store' to GET /list/ and /config/ (matching /source + /status)
  so a post-mutation re-list or fresh Load can't be served a stale response;
  the JS caches still cover the hot path

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

* feat(msm): sharpen action-panel confirm flow + fix target tri-state

- Source tab collapses to a single Status column (link+status icon pair,
  matching the linked-sites matrix); drop the Link Status text column.
- Confirm names every target site (remove the "+N more" truncation).
- Confirm highlights affected cells/rows; unaffected rows and un-applied
  cells in affected rows are dimmed (not hidden) so the highlight stands
  out; affected cells inside collapsed columns auto-reveal and restore on
  dismiss/execute.
- Disable action buttons until the active view's cells/rows finish loading,
  with a "Calculating status…" hint — prevents acting on only the loaded
  subset (unloaded cells are silently skipped, and source resolution walks
  loaded ancestor cells).
- Dismiss a pending confirm when the in-scope target set changes, matching
  page-selection and tab changes, so message + highlight + auto-reveal can't
  go stale.
- Fix tri-state target checkbox: clicking an indeterminate parent now checks
  the whole subtree (was wiping it and leaving the checkbox visually checked
  but logically unchecked). toggleTarget keys off full-checkedness, not parent
  membership.

Pure decision logic (ancestorsToExpand, matrixComplete/sourceComplete,
toggleTarget) lives in action-panel.model.js with node:test coverage.

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

* feat(msm): regroup action bar into Linked/Detached rows + clearer confirms

- Action bar is now two labeled rows grouped by the cell state each set of
  actions targets: Linked (Publish / Preview / Detach) and Detached (Merge /
  Replace / Reconnect). Button weight is positional — filled / gray outline /
  red outline (sl-button has no quiet tier; red outline serves as tertiary).
- Confirm messages drop the parenthetical verb suffix, use a fitting
  preposition (publish/preview "to" sites, copy actions "at" sites), drop the
  "linked" qualifier that clashed with the Detached row label, and add a muted
  clarifier line on the copy-affecting actions (Detach/Merge/Replace/Reconnect).

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

* feat(msm): name source→target in confirms and align plugin wording

App (action-panel):
- Confirm messages name the source and target sites, derived from the
  execution grouping so multi-level work that resolves to several sources
  drops the "from" rather than claiming a wrong single source.
- Locked phrasing: Publish/Preview "from {src} to {tgt}"; Merge/Replace/Detach
  "in {tgt} from {src}"; Reconnect "in {tgt} to {src}".

Plugin dialog:
- Adopt the same wording via a centralized _confirmText() rendered as
  message + clarifier note. Publish defers its target list to the scope chips
  ("…to these linked sites:"); Detach/Reconnect match the app; Sync uses the
  "Sync" umbrella with a Merge-vs-Replace note.
- Remove Reconnect from the Source menu: it was broken (confirm never rendered)
  and would delete the page being edited — that cleanup belongs in the app.

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

* fix(msm): give matrix page column a min-width so source view isn't cramped

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

* chore(msm): point plugin→app deep-link ref at msm-ux-rewrite

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

* refactor(msm): share source resolution between app and plugin

The plugin's sync/detach copied from the root site instead of the
nearest detached ancestor, diverging from its own confirm text and the
app's effectiveSource (the global→eu→france case ravuthu flagged).

Extract the resolution logic into core/source-tree.js
(buildParentMap/effectiveSource/isOutOfSync) and have both the app's
action-panel.model.js and the plugin delegate to it, so the two views
can't drift again. Add a core unit test covering the ancestor chain.

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

* docs(msm): correct READMEs to match shared-core reality

- app: add missing core/ files (source-tree.js, icons.js, img/)
- plugin: drop removed source-context Reconnect from behavior matrix
- plugin: fix dependency table (DA_ORIGIN, own buttons vs sl/se, core icons)

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread tools/plugins/msm/msm.js Outdated
Comment thread tools/plugins/msm/msm.js Outdated
Comment thread tools/apps/msm/helpers/column-browser.js Outdated
Comment thread docs/superpowers/specs/2026-05-14-msm-multi-level-inheritance-design.md Outdated
Comment thread tools/apps/msm/helpers/action-panel.js Outdated
Comment thread docs/msm-timestamp-noise.md Outdated
shsteimer and others added 2 commits June 4, 2026 15:40
The notes are superseded by the MSM overview deck, kept as a local artifact under .agents/diagrams/. Also ignore docs/ in .hlxignore so dev docs aren't served by Edge Delivery.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ignore .agents/diagrams/ (local-only diagram/deck output) and .playwright-cli/ scratch dirs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Import PUBLISH_LAG_MS from core/fetch.js instead of redeclaring it in the plugin
- Update deep-link ref param from msm-ux-rewrite to msm now that the branch is merged
- Remove vestigial _dispatchDeepLinkConsumed() call (method never existed, no handler in app)
- Use .finally() instead of .then() in runPool so the slot is released on rejection too
- Delete superseded MSM multi-level inheritance design spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of a hardcoded branch name, parse import.meta.url to determine
which ref to pass to the MSM app deep link:
- localhost → ref=local (loads app from localhost:3000)
- feature branch → ref=<branch>
- main → no ref param (app defaults to main)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Only trust the branch extraction when the hostname ends with .aem.page
or .aem.live — anything else (custom domain, CI, da.live itself) falls
through to null so no bogus ref is passed to the app deep link.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ravuthu
ravuthu merged commit 54e322b into main Jun 5, 2026
2 checks passed
@ravuthu
ravuthu deleted the msm branch June 5, 2026 13:47
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.

4 participants