From 6bfb1e645ad82482e77d63c99e710d93befa7c7a Mon Sep 17 00:00:00 2001 From: agent_coder Date: Fri, 10 Jul 2026 05:08:43 +0300 Subject: [PATCH] =?UTF-8?q?fix(client):=20sidebar-=D0=BA=D0=B5=D1=88=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=B4=D0=BE=D0=BB=D0=B6=D0=B5=D0=BD=20=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=B0=D1=82=D1=8C=20icon/title=20=D0=BD?= =?UTF-8?q?=D0=B0=20field-only=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8?= =?UTF-8?q?=D0=B8=20(=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20#360)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit invalidateOnUpdatePage для sidebar-pages кеша спредил сыро {...sidebarPage, title, icon} — при title-only событии icon приходит undefined и затирался (и наоборот). Embed-tree путь 20 строками выше уже гардит undefined; sidebar-ветка пропустила тот же гард. Применён тот же паттерн: ...(title!==undefined?{title}:{}) + ...(icon!==undefined?{icon}:{}). +2 теста (sidebar title-only/icon-only: непереданное поле не затирается); мутационно (вернуть сырой спред -> тесты краснеют). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../queries/invalidate-on-update-page.test.ts | 59 +++++++++++++++++++ .../src/features/page/queries/page-query.ts | 9 ++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/apps/client/src/features/page/queries/invalidate-on-update-page.test.ts b/apps/client/src/features/page/queries/invalidate-on-update-page.test.ts index 38b7f883..b2d0fa7a 100644 --- a/apps/client/src/features/page/queries/invalidate-on-update-page.test.ts +++ b/apps/client/src/features/page/queries/invalidate-on-update-page.test.ts @@ -75,6 +75,65 @@ describe("invalidateOnUpdatePage — pointwise embed-cache patch", () => { expect(p1.title).toBe("Keep"); }); + // The sidebar-pages cache (InfiniteData) is patched on the same event. It must + // carry the SAME undefined-guard as the embed path above — otherwise a + // title-only event's icon:undefined would wipe the sidebar entry's icon. + const sidebarKey = ["sidebar-pages", { pageId: "parent-1", spaceId: "s1" }]; + const seedSidebar = () => + h.qc.setQueryData(sidebarKey, { + pageParams: [undefined], + pages: [ + { + items: [ + { id: "p1", title: "Old", icon: "📄", spaceId: "s1" } as IPage, + { id: "p2", title: "Other", icon: "📁", spaceId: "s1" } as IPage, + ], + }, + ], + }); + const sidebarItem = (id: string) => { + const data = h.qc.getQueryData(sidebarKey) as { + pages: { items: IPage[] }[]; + }; + return data.pages[0].items.find((p) => p.id === id)!; + }; + + it("sidebar cache: title-only event updates title but PRESERVES the icon", () => { + seedSidebar(); + + invalidateOnUpdatePage( + "s1", + "parent-1", + "p1", + "New Title", + undefined as unknown as string, + ); + + const p1 = sidebarItem("p1"); + expect(p1.title).toBe("New Title"); + expect(p1.icon).toBe("📄"); // preserved, not wiped + // Sibling untouched. + const p2 = sidebarItem("p2"); + expect(p2.title).toBe("Other"); + expect(p2.icon).toBe("📁"); + }); + + it("sidebar cache: icon-only event updates icon but PRESERVES the title", () => { + seedSidebar(); + + invalidateOnUpdatePage( + "s1", + "parent-1", + "p1", + undefined as unknown as string, + "🚀", + ); + + const p1 = sidebarItem("p1"); + expect(p1.icon).toBe("🚀"); + expect(p1.title).toBe("Old"); // preserved, not wiped + }); + it("does not touch a subtree that lacks the updated node", () => { const otherKey = ["page-tree", "unrelated"]; const before = [ diff --git a/apps/client/src/features/page/queries/page-query.ts b/apps/client/src/features/page/queries/page-query.ts index 525070b8..4d43ab2b 100644 --- a/apps/client/src/features/page/queries/page-query.ts +++ b/apps/client/src/features/page/queries/page-query.ts @@ -636,7 +636,14 @@ export function invalidateOnUpdatePage( ...page, items: page.items.map((sidebarPage: IPage) => sidebarPage.id === id - ? { ...sidebarPage, title: title, icon: icon } + ? { + ...sidebarPage, + // Guard undefined so a title-only event can't wipe the icon + // (and vice versa) in the sidebar-pages cache — mirrors the + // embed-cache patch above. + ...(title !== undefined ? { title } : {}), + ...(icon !== undefined ? { icon } : {}), + } : sidebarPage, ), })),