Skip to content
Open
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
56 changes: 56 additions & 0 deletions packages/ui/src/components/instance/instance-shell2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
unpinRight: unpinRightDrawer,
closeLeft: closeLeftDrawer,
closeRight: closeRightDrawer,
closeFloatingDrawersIfAny,
leftAppBarButtonLabel,
rightAppBarButtonLabel,
leftAppBarButtonIcon,
Expand All @@ -202,6 +203,45 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
handleRightAppBarButtonClick,
} = drawerChrome

// When the user switches away from this instance (e.g., taps a different
// instance/project tab while a floating drawer is open on phone), close any
// open floating drawers so the previous instance's drawer doesn't remain
// visually or interactively open when its tab regains focus later.
let wasActiveInstance = Boolean(props.isActiveInstance)
createEffect(() => {
const isActive = Boolean(props.isActiveInstance)
if (wasActiveInstance && !isActive) {
closeFloatingDrawersIfAny()
}
wasActiveInstance = isActive
})

onMount(() => {
if (typeof document === "undefined") return

const handleFloatingDrawerPointerDown = (event: PointerEvent) => {
if (!props.isActiveInstance) return

const hasFloatingDrawerOpen = (!leftPinned() && leftOpen()) || (!rightPinned() && rightOpen())
if (!hasFloatingDrawerOpen) return

const target = event.target
if (!(target instanceof Node)) return

const leftContent = leftDrawerContentEl()
const rightContent = rightDrawerContentEl()
const leftPaper = leftContent?.closest(".MuiDrawer-paper")
const rightPaper = rightContent?.closest(".MuiDrawer-paper")
if (leftPaper?.contains(target) || rightPaper?.contains(target)) return

if (!leftPinned() && leftOpen()) setLeftOpen(false)
if (!rightPinned() && rightOpen()) setRightOpen(false)
}

document.addEventListener("pointerdown", handleFloatingDrawerPointerDown, true)
onCleanup(() => document.removeEventListener("pointerdown", handleFloatingDrawerPointerDown, true))
})

createEffect(() => {
const instanceId = props.instance.id
loadBackgroundProcesses(instanceId).catch((error) => {
Expand Down Expand Up @@ -607,7 +647,12 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
ModalProps={modalProps}
sx={{
zIndex: 60,
// The tab bar sits outside the floating drawer. Let its controls
// receive the gesture; click-away handling above still closes the
// drawer when the target is not inside the drawer content.
pointerEvents: "none",
"& .MuiDrawer-paper": {
pointerEvents: "auto",
width: isPhoneLayout() ? "100vw" : `${sessionSidebarWidth()}px`,
boxSizing: "border-box",
borderInlineEnd: isPhoneLayout() ? "none" : "1px solid var(--border-base)",
Expand All @@ -620,8 +665,13 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
height: floatingHeight(),
},

// Keep backdrop dismissal for the area below the tab bar without
// covering the tab bar itself.
"& .MuiBackdrop-root": {
pointerEvents: "auto",
backgroundColor: "transparent",
top: floatingTopPx(),
height: floatingHeight(),
},
}}
>
Expand Down Expand Up @@ -723,7 +773,10 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
ModalProps={modalProps}
sx={{
zIndex: 60,
// See the matching override on the left drawer for rationale.
pointerEvents: "none",
"& .MuiDrawer-paper": {
pointerEvents: "auto",
width: isPhoneLayout() ? "100vw" : `${rightDrawerWidth()}px`,
boxSizing: "border-box",
borderInlineStart: isPhoneLayout() ? "none" : "1px solid var(--border-base)",
Expand All @@ -736,7 +789,10 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
height: floatingHeight(),
},
"& .MuiBackdrop-root": {
pointerEvents: "auto",
backgroundColor: "transparent",
top: floatingTopPx(),
height: floatingHeight(),
},
}}
>
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/components/instance/shell/useDrawerChrome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface DrawerChromeApi {
unpinRight: () => void
closeLeft: () => void
closeRight: () => void
closeFloatingDrawersIfAny: () => boolean
leftAppBarButtonLabel: Accessor<string>
rightAppBarButtonLabel: Accessor<string>
leftAppBarButtonIcon: Accessor<JSX.Element>
Expand Down Expand Up @@ -250,6 +251,7 @@ export function useDrawerChrome(options: UseDrawerChromeOptions): DrawerChromeAp
unpinRight,
closeLeft,
closeRight,
closeFloatingDrawersIfAny,
leftAppBarButtonLabel,
rightAppBarButtonLabel,
leftAppBarButtonIcon,
Expand Down
8 changes: 8 additions & 0 deletions packages/ui/src/components/message-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,7 @@ export default function MessageBlock(props: MessageBlockProps) {
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
selectedMessageIds={props.selectedMessageIds}
onToggleSelectedMessage={props.onToggleSelectedMessage}
onContentRendered={props.onContentRendered}
/>
</Match>
<Match when={item().type === "compaction"}>
Expand Down Expand Up @@ -1135,6 +1136,7 @@ interface StepCardProps {
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
selectedMessageIds?: () => Set<string>
onToggleSelectedMessage?: (messageId: string, selected: boolean) => void
onContentRendered?: () => void
}

interface CompactionCardProps {
Expand Down Expand Up @@ -1317,6 +1319,12 @@ function StepCard(props: StepCardProps) {

const finishStyle = () => (props.borderColor ? { "border-left-color": props.borderColor } : undefined)

createEffect(() => {
if (props.kind !== "finish") return
if (!usageStats()) return
props.onContentRendered?.()
})

const canDeleteMessage = () =>
Boolean(props.showDeleteMessage && props.instanceId && props.sessionId && props.messageId) && !deletingMessage()

Expand Down
Loading
Loading