Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 7c457dd

Browse files
authored
fix(ui): stop the space header remounting on every tab switch
Each space page pushed its own ChannelHeader through the header store on mount and nulled it on unmount, so switching tabs tore down and rebuilt the whole header — breadcrumb, tab strip, indicator — which read as a flicker. WebsiteLayout now renders one persistent ChannelHeader for the five page routes (page derived from the pathname), so a tab switch just updates the page prop and the indicator slides. Task detail, new task, canvases, and legacy-layout scenes keep the store path unchanged. Generated-By: PostHog Code Task-Id: 0331ac58-0a1c-4b4e-b884-2ff7d8e71986
1 parent 75c853e commit 7c457dd

1 file changed

Lines changed: 24 additions & 3 deletions

File tree

‎packages/ui/src/features/canvas/components/WebsiteLayout.tsx‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from "@posthog/quill";
2525
import { ANALYTICS_EVENTS } from "@posthog/shared/analytics-events";
2626
import { ChannelBreadcrumb } from "@posthog/ui/features/canvas/components/ChannelBreadcrumb";
27+
import { ChannelHeader } from "@posthog/ui/features/canvas/components/ChannelHeader";
2728
import { iconForTemplate } from "@posthog/ui/features/canvas/components/canvasTemplateIcon";
2829
import {
2930
channelPageIcon,
@@ -63,7 +64,7 @@ import {
6364
useParams,
6465
useRouterState,
6566
} from "@tanstack/react-router";
66-
import { type ReactNode, useState } from "react";
67+
import { type ReactNode, useMemo, useState } from "react";
6768

6869
function threadIdFor(dashboardId: string): string {
6970
return `dashboard:${dashboardId}`;
@@ -429,6 +430,26 @@ export function WebsiteLayout() {
429430
? "Space"
430431
: "Channel";
431432

433+
// The five space pages render one persistent header here rather than each
434+
// scene pushing its own copy through the header store: a header that
435+
// remounts on every tab switch flashes, while a stable instance lets the
436+
// tab indicator slide. Scenes still push (harmlessly) for the legacy
437+
// layout; everything else (task detail, new task, mirrored pages) keeps
438+
// the store path.
439+
const channelPage = useMemo(() => {
440+
if (!spacesLayout || !channelId) return null;
441+
if (pathname === base) return "home" as const;
442+
if (pathname === `${base}/context`) return "context" as const;
443+
if (pathname === `${base}/loops`) return "loops" as const;
444+
if (pathname === `${base}/artifacts`) return "artifacts" as const;
445+
if (pathname === `${base}/history`) return "history" as const;
446+
return null;
447+
}, [spacesLayout, channelId, base, pathname]);
448+
const spaceHeader =
449+
channelPage && channelId ? (
450+
<ChannelHeader channelId={channelId} page={channelPage} />
451+
) : null;
452+
432453
const isDashboardDetail = Boolean(channelId && dashboardId);
433454
// The canvases grid (its own sub-route now that the channel index is the
434455
// static homepage, which carries its own header content).
@@ -446,7 +467,7 @@ export function WebsiteLayout() {
446467
new task, CONTEXT.md) pushes its "# channel / leaf" breadcrumb into
447468
the header store, as do channel-less mirrored pages (Home, Skills, …).
448469
Hidden when the canvas toolbar is showing (grid / a single canvas). */}
449-
{!showToolbar && headerContent && (
470+
{!showToolbar && (spaceHeader ?? headerContent) && (
450471
<Flex
451472
align="center"
452473
gap="2"
@@ -460,7 +481,7 @@ export function WebsiteLayout() {
460481
justify="between"
461482
className="min-w-0 flex-1 overflow-hidden"
462483
>
463-
{headerContent}
484+
{spaceHeader ?? headerContent}
464485
</Flex>
465486
{channelTask && <TaskHeaderActions task={channelTask} />}
466487
</Flex>

0 commit comments

Comments
 (0)