"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 */}
); });