Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/**
Expand Down Expand Up @@ -1897,6 +1907,7 @@ export const SessionChatInterface = memo(
titleSyncing,
subHeader,
hideMessageArea = false,
readOnly = false,
syncEnabled = !hideMessageArea,
isVisible = true,
isExternalHistoryRefreshing = false,
Expand Down Expand Up @@ -5742,12 +5753,18 @@ export const SessionChatInterface = memo(
</ErrorBoundary>
</div>

{/* Floating permission request - shown when session is waiting for permission */}
<FloatingPermissionRequest
sessionId={session.id}
sessionStatus={liveSessionStatus ?? undefined}
sessionHistory={permissionSessionHistory}
/>
{/* 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 : (
<FloatingPermissionRequest
sessionId={session.id}
sessionStatus={liveSessionStatus ?? undefined}
sessionHistory={permissionSessionHistory}
/>
)}

{/* Notification permission prompt - shown when session becomes idle (turn completed) */}
{/* TODO(analytics): session/notification_prompt_shown|_permission_granted|_permission_denied.
Expand Down Expand Up @@ -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 : (
<SessionChatInputArea
ref={inputAreaRef}
session={session}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -664,12 +664,16 @@ const SessionDetail = ({
urlPrNumber,
urlBrowser,
onMobileBack,
readOnly = false,
}: {
sessionId: SessionId;
urlTab?: string;
urlPrNumber?: number;
urlBrowser?: boolean;
onMobileBack?: () => 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();
Expand Down Expand Up @@ -4923,6 +4927,7 @@ const SessionDetail = ({
workspaceSession={activeSession}
className="h-full"
hideHeader
readOnly={readOnly}
syncEnabled={isActive || pendingForkSourceId !== undefined}
isVisible={isActive}
isChildTab={tabSession.id !== sessionId}
Expand Down Expand Up @@ -5550,6 +5555,7 @@ const SessionDetail = ({
workspaceSession: activeSession,
className: 'h-full',
hideHeader: true,
readOnly,
syncEnabled: isActive || pendingForkSourceId !== undefined,
isVisible,
onFileDiffClick: handleOpenFileDiffForChat,
Expand Down