From 7256adf63728b2ecd92b59a68663db79246fc02e Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Sat, 4 Jul 2026 16:12:21 +0700 Subject: [PATCH] fix(web): scope Suspense to page body; guard formatConfig against null config Co-Authored-By: Claude Opus 4.8 (1M context) --- web/src/App.test.tsx | 4 +++- web/src/App.tsx | 32 ++++++++++++++++++-------------- web/src/pages/ChannelsPage.tsx | 2 +- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/web/src/App.test.tsx b/web/src/App.test.tsx index 2b72b2f..7568f28 100644 --- a/web/src/App.test.tsx +++ b/web/src/App.test.tsx @@ -24,7 +24,9 @@ test("renders navigation and redirects to domains", async () => { ) // Sidebar nav also renders a "Domains" link label, so scope the assertion // to the routed page content to unambiguously confirm the redirect + page. + // Suspense is now scoped inside
, so
mounts with the loading + // fallback first — await the lazy chunk resolving to the actual page text. const main = await screen.findByRole("main") - expect(within(main).getByText("Domains")).toBeInTheDocument() + expect(await within(main).findByText("Domains")).toBeInTheDocument() expect(screen.getByRole("link", { name: /domains/i })).toBeInTheDocument() }) diff --git a/web/src/App.tsx b/web/src/App.tsx index a83f54c..dea713a 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -17,28 +17,32 @@ const ChannelsPage = lazy(() => import("@/pages/ChannelsPage").then((m) => ({ de // Every non-auth route shares the same guard + chrome; wrapping here keeps // each below a one-liner instead of repeating both on every page. +// Suspense is scoped to just the page body (not Layout) so lazy-loading a +// route doesn't collapse the sidebar/header chrome to the fallback on nav. function Protected({ children }: { children: ReactNode }) { return ( - {children} + + Загрузка…}> + {children} + + ) } export function App() { return ( - Загрузка…}> - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + ) } diff --git a/web/src/pages/ChannelsPage.tsx b/web/src/pages/ChannelsPage.tsx index f224640..4abf430 100644 --- a/web/src/pages/ChannelsPage.tsx +++ b/web/src/pages/ChannelsPage.tsx @@ -74,7 +74,7 @@ const EMPTY_FORM: ChannelForm = { type: "telegram", chatId: "", botToken: "", ur // (Object.entries по всему config печатал бы любое поле, включая случайно // сохранённый секрет). function formatConfig(type: string, config: object): string { - const c = config as Record + const c = (config ?? {}) as Record if (type === "telegram") return c.chat_id ? `chat_id: ${String(c.chat_id)}` : "" if (type === "webhook") return c.url ? `url: ${String(c.url)}` : "" return ""