diff --git a/packages/components/src/components/sessions/session-chat-interface.tsx b/packages/components/src/components/sessions/session-chat-interface.tsx index c16dd80fc..bee5daa0f 100644 --- a/packages/components/src/components/sessions/session-chat-interface.tsx +++ b/packages/components/src/components/sessions/session-chat-interface.tsx @@ -1728,6 +1728,16 @@ interface SessionChatInterfaceProps { subHeader?: React.ReactNode; /** When true, hide the message area and input but keep the header and subHeader visible */ hideMessageArea?: boolean; + /** + * When true, this surface follows the session without driving it: the + * composer and the permission request's response buttons are not rendered. + * + * For a host that mounts a session the viewer may read but not write. It is + * presentation only — a viewer who cannot write is enforced wherever the + * writes are applied, not here — but a control that cannot work should not be + * offered. + */ + readOnly?: boolean; /** When false, keep the local doc mounted without holding the remote room subscription. */ syncEnabled?: boolean; /** @@ -1897,6 +1907,7 @@ export const SessionChatInterface = memo( titleSyncing, subHeader, hideMessageArea = false, + readOnly = false, syncEnabled = !hideMessageArea, isVisible = true, isExternalHistoryRefreshing = false, @@ -5742,12 +5753,18 @@ export const SessionChatInterface = memo( - {/* Floating permission request - shown when session is waiting for permission */} - + {/* Floating permission request - shown when session is waiting for permission. + A read-only surface does not render it: its options are + answers, and an answer this viewer cannot write is a + button that does nothing. The request still appears in the + transcript. */} + {readOnly ? null : ( + + )} {/* Notification permission prompt - shown when session becomes idle (turn completed) */} {/* TODO(analytics): session/notification_prompt_shown|_permission_granted|_permission_denied. @@ -5856,7 +5873,7 @@ export const SessionChatInterface = memo( {/* Input area - isolated component to prevent full re-renders on typing. Hidden while a permission is pending so the response buttons claim the bottom surface; chat queue is bypassed for the same reason. */} - {shouldReplaceComposerWithPermission ? null : ( + {readOnly || shouldReplaceComposerWithPermission ? null : ( void; + /** Follow the session without driving it. Passed to every chat surface this + * page mounts; see `SessionChatInterfaceProps.readOnly`. */ + readOnly?: boolean; }) => { const { t } = useTranslation(); const router = useRouter(); @@ -4923,6 +4927,7 @@ const SessionDetail = ({ workspaceSession={activeSession} className="h-full" hideHeader + readOnly={readOnly} syncEnabled={isActive || pendingForkSourceId !== undefined} isVisible={isActive} isChildTab={tabSession.id !== sessionId} @@ -5550,6 +5555,7 @@ const SessionDetail = ({ workspaceSession: activeSession, className: 'h-full', hideHeader: true, + readOnly, syncEnabled: isActive || pendingForkSourceId !== undefined, isVisible, onFileDiffClick: handleOpenFileDiffForChat,