(function UserI
return (
{
+ composerOwnsFocusRef.current = true
+ }}
+ onBlurCapture={(event) => {
+ if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {
+ composerOwnsFocusRef.current = false
+ }
+ }}
className={cn(
'relative z-10 mx-auto w-full max-w-chat cursor-text rounded-2xl border border-[var(--border-1)] bg-[var(--white)] px-2.5 py-2 dark:bg-[var(--surface-4)]',
isInitialView && 'shadow-ambient'
diff --git a/apps/sim/app/workspace/[workspaceId]/home/home.tsx b/apps/sim/app/workspace/[workspaceId]/home/home.tsx
index 03008c597ae..b274c12b864 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/home.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/home.tsx
@@ -3,6 +3,7 @@
import {
type Dispatch,
lazy,
+ type PointerEvent,
type SetStateAction,
Suspense,
useCallback,
@@ -10,7 +11,6 @@ import {
useMemo,
useRef,
useState,
- useSyncExternalStore,
} from 'react'
import { Button, cn, toast } from '@sim/emcn'
import { PanelLeft } from '@sim/emcn/icons'
@@ -27,7 +27,6 @@ import {
LandingWorkflowSeedStorage,
MothershipHandoffStorage,
} from '@/lib/core/utils/browser-storage'
-import { isDesktopApp } from '@/lib/desktop'
import {
addMothershipContexts,
MOTHERSHIP_SEND_MESSAGE_EVENT,
@@ -65,8 +64,6 @@ import type {
} from './types'
const logger = createLogger('Home')
-const subscribeToDesktopApp = () => () => {}
-const getServerDesktopAppSnapshot = () => false
/**
* The resource preview panel pulls in the file-viewer stack (rich-markdown
@@ -89,11 +86,6 @@ interface HomeProps {
export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps) {
useOAuthReturnRouter()
- const isDesktop = useSyncExternalStore(
- subscribeToDesktopApp,
- isDesktopApp,
- getServerDesktopAppSnapshot
- )
const { workspaceId } = useParams<{ workspaceId: string }>()
const router = useRouter()
const queryClient = useQueryClient()
@@ -212,13 +204,32 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
const [isResourceCollapsed, setIsResourceCollapsed] = useState(true)
const [skipResourceTransition, setSkipResourceTransition] = useState(false)
+ const [resourceActivityIds, setResourceActivityIds] = useState>(new Set())
const isResourceCollapsedRef = useRef(isResourceCollapsed)
isResourceCollapsedRef.current = isResourceCollapsed
-
- function handleResourceEvent() {
- if (isResourceCollapsedRef.current) {
- setIsResourceCollapsed(false)
+ const userOwnsResourceViewRef = useRef(false)
+ const activeResourceParamRef = useRef(activeResourceParam)
+ activeResourceParamRef.current = activeResourceParam
+
+ function handleResourceEvent(resourceId: string) {
+ // Agent work should always make the resource surface available. Expanding
+ // the panel is independent from selecting a resource: once the user has
+ // chosen another resource, the agent may work in the background without
+ // taking that selection away.
+ if (isResourceCollapsedRef.current) setIsResourceCollapsed(false)
+
+ const activeResourceId = activeResourceParamRef.current
+ if (userOwnsResourceViewRef.current && activeResourceId && activeResourceId !== resourceId) {
+ setResourceActivityIds((current) => new Set(current).add(resourceId))
+ return
}
+ setResourceActivityIds((current) => {
+ if (!current.has(resourceId)) return current
+ const next = new Set(current)
+ next.delete(resourceId)
+ return next
+ })
+ if (activeResourceId !== resourceId) setActiveResourceUrl(resourceId)
}
const {
@@ -263,13 +274,58 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
)
const { mothershipRef, handleResizePointerDown, clearWidth } = useMothershipResize(desktopScopeId)
+ const effectiveActiveResourceIdRef = useRef(activeResourceId)
+ effectiveActiveResourceIdRef.current = activeResourceId
+ const resourceAttentionChatIdRef = useRef(resolvedChatId)
const collapseResource = useCallback(() => {
+ userOwnsResourceViewRef.current = true
clearWidth()
setIsResourceCollapsed(true)
}, [clearWidth])
+ const selectResourceFromUser = useCallback(
+ (resourceId: string) => {
+ userOwnsResourceViewRef.current = true
+ setResourceActivityIds((current) => {
+ if (!current.has(resourceId)) return current
+ const next = new Set(current)
+ next.delete(resourceId)
+ return next
+ })
+ if (effectiveActiveResourceIdRef.current === resourceId) return
+ effectiveActiveResourceIdRef.current = resourceId
+ activeResourceParamRef.current = resourceId
+ setActiveResourceId(resourceId)
+ },
+ [setActiveResourceId]
+ )
+
+ const addResourceFromUser = useCallback(
+ (resource: MothershipResource) => {
+ userOwnsResourceViewRef.current = true
+ addResource(resource)
+ selectResourceFromUser(resource.id)
+ setIsResourceCollapsed(false)
+ },
+ [addResource, selectResourceFromUser]
+ )
+
+ const handleResourceResizePointerDown = useCallback(
+ (event: PointerEvent) => {
+ userOwnsResourceViewRef.current = true
+ handleResizePointerDown(event)
+ },
+ [handleResizePointerDown]
+ )
+
+ const handleResourceInteraction = useCallback(() => {
+ userOwnsResourceViewRef.current = true
+ }, [])
+
useEffect(() => {
+ const previousChatId = resourceAttentionChatIdRef.current
+ resourceAttentionChatIdRef.current = resolvedChatId
wasSendingRef.current = false
if (resolvedChatId) {
markRead(resolvedChatId)
@@ -277,6 +333,10 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
clearWidth()
setIsResourceCollapsed(true)
}
+ if (!resolvedChatId || (previousChatId && previousChatId !== resolvedChatId)) {
+ userOwnsResourceViewRef.current = false
+ setResourceActivityIds(new Set())
+ }
}, [resolvedChatId, markRead, clearWidth])
useEffect(() => {
@@ -287,7 +347,12 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
}, [isSending, resolvedChatId, markRead])
useEffect(() => {
- if (!(resources.length > 0 && isResourceCollapsedRef.current)) return
+ if (
+ !(resources.length > 0 && isResourceCollapsedRef.current) ||
+ userOwnsResourceViewRef.current
+ ) {
+ return
+ }
setIsResourceCollapsed(false)
setSkipResourceTransition(true)
const id = requestAnimationFrame(() => setSkipResourceTransition(false))
@@ -296,9 +361,18 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
useEffect(() => {
if (resources.length === 0 && !isResourceCollapsedRef.current) {
- collapseResource()
+ clearWidth()
+ setIsResourceCollapsed(true)
}
- }, [resources, collapseResource])
+ }, [resources, clearWidth])
+
+ useEffect(() => {
+ const resourceIds = new Set(resources.map((resource) => resource.id))
+ setResourceActivityIds((current) => {
+ const next = new Set([...current].filter((id) => resourceIds.has(id)))
+ return next.size === current.size ? current : next
+ })
+ }, [resources])
const handleStopGeneration = useCallback(() => {
captureEvent(posthogRef.current, 'task_generation_aborted', {
@@ -325,6 +399,8 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
setIsInputEntering(true)
}
+ userOwnsResourceViewRef.current = false
+ setResourceActivityIds(new Set())
sendMessage(trimmed || 'Analyze the attached file(s).', fileAttachments, contexts)
},
[workspaceId, chatId, sendMessage]
@@ -425,8 +501,7 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
function handleContextAdd(context: ChatContext) {
const resolved = resolveResourceFromContext(context)
if (resolved) {
- addResource({ ...resolved, title: resourceTitleForContext(context) })
- handleResourceEvent()
+ addResourceFromUser({ ...resolved, title: resourceTitleForContext(context) })
}
}
@@ -446,11 +521,7 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
}
function openWorkspaceResource(resource: MothershipResource) {
- const wasAdded = addResource(resource)
- if (!wasAdded) {
- setActiveResourceId(resource.id)
- }
- handleResourceEvent()
+ addResourceFromUser(resource)
}
/**
@@ -513,9 +584,7 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
className={cn(
'absolute z-10',
RESOURCE_HEADER_CLASSES.contentTop,
- isDesktop || isResourceCollapsed
- ? RESOURCE_HEADER_CLASSES.adjacentEndPosition
- : RESOURCE_HEADER_CLASSES.endPosition
+ RESOURCE_HEADER_CLASSES.adjacentEndPosition
)}
>
@@ -589,14 +658,14 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
role='separator'
aria-orientation='vertical'
aria-label='Resize resource panel'
- onPointerDown={handleResizePointerDown}
+ onPointerDown={handleResourceResizePointerDown}
/>
)}
- {isDesktop ? (
-
+
- ) : (
- isResourceCollapsed && (
- 0 && (
+
)}
- >
-
setIsResourceCollapsed(false)}
- className='size-[30px] rounded-[8px] hover-hover:bg-[var(--surface-active)]'
- aria-label='Expand resource view'
- >
-
-
-
- )
- )}
+
+
+