"use client";
import { memo, useCallback } from "react";
import { SessionMobileTopBar } from "./session-mobile-top-bar";
import { SessionMobileBottomNav } from "./session-mobile-bottom-nav";
import { SessionTaskSwitcherSheet } from "./session-task-switcher-sheet";
import { TaskChatPanel } from "../task-chat-panel";
import { TaskPlanPanel } from "../task-plan-panel";
import { TaskChangesPanel } from "../task-changes-panel";
import { TaskFilesPanel } from "../task-files-panel";
import { ShellTerminal } from "../shell-terminal";
import { PassthroughTerminal } from "../passthrough-terminal";
import { MobileTerminalKeybar, KEYBAR_HEIGHT_PX } from "./mobile-terminal-keybar";
import { SessionPanelContent } from "@kandev/ui/pannel-session";
import { useSessionLayoutState } from "@/hooks/use-session-layout-state";
import { useVisualViewportOffset } from "@/hooks/use-visual-viewport-offset";
import type { MobileSessionPanel } from "@/lib/state/slices/ui/types";
const TOP_NAV_HEIGHT = "3.5rem";
const BOTTOM_NAV_HEIGHT = "3.25rem";
type SessionMobileLayoutProps = {
workspaceId: string | null;
workflowId: string | null;
sessionId?: string | null;
baseBranch?: string;
worktreeBranch?: string | null;
taskTitle?: string;
isRemoteExecutor?: boolean;
remoteExecutorType?: string | null;
remoteExecutorName?: string | null;
remoteState?: string | null;
remoteCreatedAt?: string | null;
remoteCheckedAt?: string | null;
remoteStatusError?: string | null;
};
function MobileChatPanelContent({
activeTaskId,
isPassthroughMode,
effectiveSessionId,
sessionId,
onOpenFile,
}: {
activeTaskId: string | null;
isPassthroughMode: boolean;
effectiveSessionId: string | null;
sessionId?: string | null;
onOpenFile: (path: string) => void;
}) {
if (activeTaskId && isPassthroughMode) {
return (
);
}
if (activeTaskId) {
return ;
}
return (
No task selected
);
}
type MobilePanelAreaProps = {
currentMobilePanel: MobileSessionPanel;
activeTaskId: string | null;
isPassthroughMode: boolean;
effectiveSessionId: string | null;
sessionId?: string | null;
selectedDiff: { path: string; content?: string } | null;
handleOpenFileFromChat: (path: string) => void;
handleClearSelectedDiff: () => void;
handleSelectDiffAndSwitchPanel: (path: string, content?: string) => void;
handleOpenFile: (file: { path: string }) => void;
topNavHeight: string;
bottomNavHeight: string;
};
function MobilePanelArea({
currentMobilePanel,
activeTaskId,
isPassthroughMode,
effectiveSessionId,
sessionId,
selectedDiff,
handleOpenFileFromChat,
handleClearSelectedDiff,
handleSelectDiffAndSwitchPanel,
handleOpenFile,
topNavHeight,
bottomNavHeight,
}: MobilePanelAreaProps) {
const { keyboardOpen, bottomOffset } = useVisualViewportOffset();
// Keep terminal content's visible bottom glued to the keybar top. When the
// keyboard is up, the content area already pads for the bottom nav (which
// is now under the keyboard), so we subtract it back out and add the
// keyboard height instead.
const terminalPaddingBottom = keyboardOpen
? `calc(${bottomOffset + KEYBAR_HEIGHT_PX}px - ${bottomNavHeight} - env(safe-area-inset-bottom, 0px))`
: `${KEYBAR_HEIGHT_PX}px`;
return (
{currentMobilePanel === "chat" && (
)}
{currentMobilePanel === "plan" && (
)}
{currentMobilePanel === "changes" && (
)}
{currentMobilePanel === "files" && (
)}
{currentMobilePanel === "terminal" && (
)}
);
}
function useMobilePanelHandlers({
handleSelectDiff,
handlePanelChange,
}: {
handleSelectDiff: (path: string, content?: string) => void;
handlePanelChange: (panel: MobileSessionPanel) => void;
}) {
const handleSelectDiffAndSwitchPanel = useCallback(
(path: string, content?: string) => {
handleSelectDiff(path, content);
handlePanelChange("changes");
},
[handleSelectDiff, handlePanelChange],
);
const handleOpenFileFromChat = useCallback(
(path: string) => {
handleSelectDiff(path);
handlePanelChange("changes");
},
[handleSelectDiff, handlePanelChange],
);
const handleOpenFile = useCallback(
(file: { path: string }) => {
handleSelectDiff(file.path);
handlePanelChange("changes");
},
[handleSelectDiff, handlePanelChange],
);
return { handleSelectDiffAndSwitchPanel, handleOpenFileFromChat, handleOpenFile };
}
export const SessionMobileLayout = memo(function SessionMobileLayout({
workspaceId,
workflowId,
sessionId,
baseBranch,
worktreeBranch,
taskTitle,
isRemoteExecutor,
remoteExecutorType,
remoteExecutorName,
remoteState,
remoteCreatedAt,
remoteCheckedAt,
remoteStatusError,
}: SessionMobileLayoutProps) {
// Use shared layout state hook
const {
activeTaskId,
effectiveSessionId,
isPassthroughMode,
selectedDiff,
handleSelectDiff,
handleClearSelectedDiff,
totalChangesCount,
hasUnseenPlanUpdate,
showApproveButton,
handleApprove,
currentMobilePanel,
handlePanelChange,
isTaskSwitcherOpen,
handleMenuClick,
setMobileSessionTaskSwitcherOpen,
} = useSessionLayoutState({ sessionId });
const { handleSelectDiffAndSwitchPanel, handleOpenFileFromChat, handleOpenFile } =
useMobilePanelHandlers({
handleSelectDiff,
handlePanelChange,
});
return (
{/* Fixed Top Bar */}
{/* Content Area - fixed height panels that manage their own scrolling */}
{/* Terminal key-bar — docks above OS keyboard or bottom nav on the terminal panel */}
{/* Fixed Bottom Navigation */}
{/* Task Switcher Sheet */}
);
});