/* Copyright 2026 Marimo. All rights reserved. */ import { useAtom, useAtomValue } from "jotai"; import { ArrowRightIcon, ChevronRightIcon } from "lucide-react"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { hasCellsAtom } from "@/core/cells/cells"; import type { AppConfig } from "@/core/config/config-schema"; import { connectionAtom } from "@/core/network/connection"; import { useConnectionNotice } from "@/core/network/useConnectionNotice"; import { sandboxAtom } from "@/core/packages/sandbox-state"; import { ConnectionState } from "@/core/websocket/types"; import { useInterval } from "@/hooks/useInterval"; import { cn } from "@/utils/cn"; import { runtimeDetailsExpandedAtom } from "../chrome/panels/runtime-details-state"; import { chromeAtom, useChromeActions } from "../chrome/state"; import { VerticalLayoutWrapper } from "../renderers/vertical-layout/vertical-layout-wrapper"; import { ConnectionDetails } from "./connection-details"; import { ConnectionStatusIcon, hasStartupStages } from "./startup-progress"; export const ConnectionNotice = ({ appConfig, onRetry, canOpenPackages = true, }: { appConfig: AppConfig; onRetry: () => void; canOpenPackages?: boolean; }) => { const notice = useConnectionNotice(); const connection = useAtomValue(connectionAtom); const hasCells = useAtomValue(hasCellsAtom); const sandbox = useAtomValue(sandboxAtom); const [detailsExpanded, setDetailsExpanded] = useAtom( runtimeDetailsExpandedAtom, ); const chrome = useAtomValue(chromeAtom); const packagesOpen = (chrome.isSidebarOpen && chrome.selectedPanel === "packages") || (chrome.isDeveloperPanelOpen && chrome.selectedDeveloperPanelTab === "packages"); const { openApplication } = useChromeActions(); // Reconnection uses the footer indicator while existing cells stay visible. const reconnecting = connection.state === ConnectionState.CONNECTING && connection.phase === "reconnecting"; const openDetails = () => { setDetailsExpanded(true); openApplication("packages"); }; if (!notice && !sandbox?.backend) { return null; } if (hasCells && canOpenPackages) { const title = notice?.ready && notice.kind === "startup" ? "Ready" : notice?.title; return ( {notice && !reconnecting && ( {title} {!notice.pending && !notice.ready && !notice.sandbox && ( Try again )} )} ); } if (!notice) { return null; } const showSteps = hasStartupStages(notice); return ( {!canOpenPackages && showSteps && ( {notice.title} )} {canOpenPackages && !notice.ready && !packagesOpen && (notice.sandbox || notice.pending) && ( {notice.pending ? "View setup details" : "Open Packages"} )} {!notice.pending && !notice.sandbox && ( Try again )} {notice.pending && ( )} ); }; function StartupElapsedTime() { const [startedAt] = useState(Date.now); const [seconds, setSeconds] = useState(0); useInterval(() => setSeconds(Math.floor((Date.now() - startedAt) / 1000)), { delayMs: 1000, whenVisible: true, }); const duration = seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`; return ( Elapsed {duration} ); }