"use client" /** * SystemBannerSlot — the live banner rendered at the top of the app shell. * Reads from `SystemBannerContext` so toggling / editing from Settings * updates the banner in real time (and cross-tab via the storage listener). */ import * as React from "react" import { useLocation } from "react-router-dom" import { Link } from "@/lib/router-compat" import { SystemBanner } from "@/components/ui/banner" import { AiThinkingOverlay } from "@/components/ui/ai-thinking-surface" import { useSystemBanner } from "@/contexts/system-banner-context" const SUPPRESS_BANNER_PATHS: ReadonlyArray = ["/builder/onboarding"] export function SystemBannerSlot() { const { config, setEnabled } = useSystemBanner() const location = useLocation() const [mounted, setMounted] = React.useState(false) React.useEffect(() => setMounted(true), []) if (!config.enabled) return null if (SUPPRESS_BANNER_PATHS.some(path => location.pathname.startsWith(path))) return null // Spans `[data-app-shell-workspace]` (secondary rail + main canvas). `z-40` keeps // the promo above inline secondary / main chrome; pointer-events-none on the wrapper // preserves sidebar header hit targets in the transparent margin. return (
setEnabled(false)} decorativeOverlay={ config.variant === "promo" && mounted ? ( ) : undefined } action={ config.actionLabel ? { label: config.actionLabel, href: config.actionHref || "#" } : undefined } > {/* Fall back gracefully if message was cleared — still show title-only banner. */} {config.message || (config.title ? "" : Details)}
) } /** Tiny local link helper so the fallback above still uses next/link. */ function LinkAccent({ href, children }: { href: string; children: React.ReactNode }) { return ( {children} ) }