fix(ws): broadcast realtime page rename/icon change (#72)

handleMessage became a no-op and PageWsListener intentionally ignored
PAGE_UPDATED, so a rename/icon change (client operation:updateOne) was no longer
rebroadcast -> other clients saw stale title/icon in the sidebar+breadcrumbs
until a reload (create/duplicate/restore were covered; updateOne regressed).
Add a server-authoritative onPageUpdated handler: PageService.update detects a
real title/icon change (DTO carries the field AND value differs; no-op/content-
only saves excluded) and attaches a treeUpdate snapshot to PAGE_UPDATED; the
listener broadcasts a tree updateOne via the restriction-aware emitTreeEvent
(so a restricted page's title never leaks). Content-only saves attach nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude code agent 227
2026-06-21 03:29:52 +03:00
parent c78177c28b
commit 6928817cee
6 changed files with 161 additions and 5 deletions

View File

@@ -270,6 +270,17 @@ export class PageService {
const isAgent = provenance?.actor === 'agent';
// Detect a real title/icon change so the WS tree listener can broadcast an
// `updateOne` to the space (rename / icon swap) WITHOUT re-broadcasting on a
// content-only save. Only treat a field as changed when the DTO actually
// carries it AND its value differs from what is already stored — a no-op
// save (same title, or a content-only update where these are undefined)
// produces no tree snapshot, so the listener stays quiet.
const titleChanged =
updatePageDto.title !== undefined && updatePageDto.title !== page.title;
const iconChanged =
updatePageDto.icon !== undefined && updatePageDto.icon !== page.icon;
await this.pageRepo.updatePage(
{
title: updatePageDto.title,
@@ -287,6 +298,22 @@ export class PageService {
contributorIds: contributorIds,
},
page.id,
undefined,
// Enrich PAGE_UPDATED only when title/icon actually changed. The snapshot
// values come from the server-side data being persisted (DTO when present,
// otherwise the unchanged stored value), never relayed from the client.
titleChanged || iconChanged
? {
treeUpdate: {
id: page.id,
slugId: page.slugId,
spaceId: page.spaceId,
parentPageId: page.parentPageId ?? null,
...(titleChanged ? { title: updatePageDto.title } : {}),
...(iconChanged ? { icon: updatePageDto.icon } : {}),
},
}
: undefined,
);
this.generalQueue