mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-11 08:12:38 +02:00
fix sticky browser issue
This commit is contained in:
parent
0bd234ddf6
commit
a76f8bae14
3 changed files with 58 additions and 17 deletions
|
|
@ -2400,6 +2400,10 @@ function App() {
|
||||||
}
|
}
|
||||||
}, [runId])
|
}, [runId])
|
||||||
|
|
||||||
|
const dismissBrowserOverlay = useCallback(() => {
|
||||||
|
setIsBrowserOpen(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleNewChat = useCallback(() => {
|
const handleNewChat = useCallback(() => {
|
||||||
// Invalidate any in-flight run loads (rapid switching can otherwise "pop" old conversations back in)
|
// Invalidate any in-flight run loads (rapid switching can otherwise "pop" old conversations back in)
|
||||||
loadRunRequestIdRef.current += 1
|
loadRunRequestIdRef.current += 1
|
||||||
|
|
@ -2623,6 +2627,7 @@ function App() {
|
||||||
|
|
||||||
// File tab operations
|
// File tab operations
|
||||||
const openFileInNewTab = useCallback((path: string) => {
|
const openFileInNewTab = useCallback((path: string) => {
|
||||||
|
dismissBrowserOverlay()
|
||||||
const existingTab = fileTabs.find(t => t.path === path)
|
const existingTab = fileTabs.find(t => t.path === path)
|
||||||
if (existingTab) {
|
if (existingTab) {
|
||||||
setActiveFileTabId(existingTab.id)
|
setActiveFileTabId(existingTab.id)
|
||||||
|
|
@ -2635,11 +2640,12 @@ function App() {
|
||||||
setActiveFileTabId(id)
|
setActiveFileTabId(id)
|
||||||
setIsGraphOpen(false)
|
setIsGraphOpen(false)
|
||||||
setSelectedPath(path)
|
setSelectedPath(path)
|
||||||
}, [fileTabs])
|
}, [fileTabs, dismissBrowserOverlay])
|
||||||
|
|
||||||
const switchFileTab = useCallback((tabId: string) => {
|
const switchFileTab = useCallback((tabId: string) => {
|
||||||
const tab = fileTabs.find(t => t.id === tabId)
|
const tab = fileTabs.find(t => t.id === tabId)
|
||||||
if (!tab) return
|
if (!tab) return
|
||||||
|
dismissBrowserOverlay()
|
||||||
setActiveFileTabId(tabId)
|
setActiveFileTabId(tabId)
|
||||||
setSelectedBackgroundTask(null)
|
setSelectedBackgroundTask(null)
|
||||||
setExpandedFrom(null)
|
setExpandedFrom(null)
|
||||||
|
|
@ -2662,7 +2668,7 @@ function App() {
|
||||||
setIsGraphOpen(false)
|
setIsGraphOpen(false)
|
||||||
setIsSuggestedTopicsOpen(false)
|
setIsSuggestedTopicsOpen(false)
|
||||||
setSelectedPath(tab.path)
|
setSelectedPath(tab.path)
|
||||||
}, [fileTabs, isRightPaneMaximized])
|
}, [fileTabs, isRightPaneMaximized, dismissBrowserOverlay])
|
||||||
|
|
||||||
const closeFileTab = useCallback((tabId: string) => {
|
const closeFileTab = useCallback((tabId: string) => {
|
||||||
const closingTab = fileTabs.find(t => t.id === tabId)
|
const closingTab = fileTabs.find(t => t.id === tabId)
|
||||||
|
|
@ -2734,8 +2740,9 @@ function App() {
|
||||||
// Create a new tab
|
// Create a new tab
|
||||||
const id = newChatTabId()
|
const id = newChatTabId()
|
||||||
setChatTabs(prev => [...prev, { id, runId: null }])
|
setChatTabs(prev => [...prev, { id, runId: null }])
|
||||||
setActiveChatTabId(id)
|
setActiveChatTabId(id)
|
||||||
}
|
}
|
||||||
|
dismissBrowserOverlay()
|
||||||
handleNewChat()
|
handleNewChat()
|
||||||
// Left-pane "new chat" should always open full chat view.
|
// Left-pane "new chat" should always open full chat view.
|
||||||
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen) {
|
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen) {
|
||||||
|
|
@ -2747,7 +2754,7 @@ function App() {
|
||||||
setSelectedPath(null)
|
setSelectedPath(null)
|
||||||
setIsGraphOpen(false)
|
setIsGraphOpen(false)
|
||||||
setIsSuggestedTopicsOpen(false)
|
setIsSuggestedTopicsOpen(false)
|
||||||
}, [chatTabs, activeChatTabId, handleNewChat, selectedPath, isGraphOpen, isSuggestedTopicsOpen])
|
}, [chatTabs, activeChatTabId, dismissBrowserOverlay, handleNewChat, selectedPath, isGraphOpen, isSuggestedTopicsOpen])
|
||||||
|
|
||||||
// Sidebar variant: create/switch chat tab without leaving file/graph context.
|
// Sidebar variant: create/switch chat tab without leaving file/graph context.
|
||||||
const handleNewChatTabInSidebar = useCallback(() => {
|
const handleNewChatTabInSidebar = useCallback(() => {
|
||||||
|
|
@ -2865,11 +2872,12 @@ function App() {
|
||||||
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen) {
|
if (selectedPath || isGraphOpen || isSuggestedTopicsOpen) {
|
||||||
setExpandedFrom({ path: selectedPath, graph: isGraphOpen, suggestedTopics: isSuggestedTopicsOpen })
|
setExpandedFrom({ path: selectedPath, graph: isGraphOpen, suggestedTopics: isSuggestedTopicsOpen })
|
||||||
}
|
}
|
||||||
|
dismissBrowserOverlay()
|
||||||
setIsRightPaneMaximized(false)
|
setIsRightPaneMaximized(false)
|
||||||
setSelectedPath(null)
|
setSelectedPath(null)
|
||||||
setIsGraphOpen(false)
|
setIsGraphOpen(false)
|
||||||
setIsSuggestedTopicsOpen(false)
|
setIsSuggestedTopicsOpen(false)
|
||||||
}, [selectedPath, isGraphOpen, isSuggestedTopicsOpen])
|
}, [selectedPath, isGraphOpen, isSuggestedTopicsOpen, dismissBrowserOverlay])
|
||||||
|
|
||||||
const handleCloseFullScreenChat = useCallback(() => {
|
const handleCloseFullScreenChat = useCallback(() => {
|
||||||
if (expandedFrom) {
|
if (expandedFrom) {
|
||||||
|
|
@ -3004,8 +3012,7 @@ function App() {
|
||||||
case 'chat':
|
case 'chat':
|
||||||
setSelectedPath(null)
|
setSelectedPath(null)
|
||||||
setIsGraphOpen(false)
|
setIsGraphOpen(false)
|
||||||
// Don't touch isBrowserOpen here — chat navigation should land in
|
setIsBrowserOpen(false)
|
||||||
// the right sidebar when the browser overlay is active.
|
|
||||||
setExpandedFrom(null)
|
setExpandedFrom(null)
|
||||||
setIsRightPaneMaximized(false)
|
setIsRightPaneMaximized(false)
|
||||||
setSelectedBackgroundTask(null)
|
setSelectedBackgroundTask(null)
|
||||||
|
|
@ -3021,7 +3028,12 @@ function App() {
|
||||||
|
|
||||||
const navigateToView = useCallback(async (nextView: ViewState) => {
|
const navigateToView = useCallback(async (nextView: ViewState) => {
|
||||||
const current = currentViewState
|
const current = currentViewState
|
||||||
if (viewStatesEqual(current, nextView)) return
|
if (viewStatesEqual(current, nextView)) {
|
||||||
|
if (isBrowserOpen) {
|
||||||
|
dismissBrowserOverlay()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cancelRecordingIfActive()
|
cancelRecordingIfActive()
|
||||||
const nextHistory = {
|
const nextHistory = {
|
||||||
|
|
@ -3030,7 +3042,7 @@ function App() {
|
||||||
}
|
}
|
||||||
setHistory(nextHistory)
|
setHistory(nextHistory)
|
||||||
await applyViewState(nextView)
|
await applyViewState(nextView)
|
||||||
}, [appendUnique, applyViewState, cancelRecordingIfActive, currentViewState, setHistory])
|
}, [appendUnique, applyViewState, cancelRecordingIfActive, currentViewState, setHistory, isBrowserOpen, dismissBrowserOverlay])
|
||||||
|
|
||||||
const navigateBack = useCallback(async () => {
|
const navigateBack = useCallback(async () => {
|
||||||
const { back, forward } = historyRef.current
|
const { back, forward } = historyRef.current
|
||||||
|
|
@ -4329,6 +4341,8 @@ function App() {
|
||||||
meetingSummarizing={meetingSummarizing}
|
meetingSummarizing={meetingSummarizing}
|
||||||
meetingAvailable={voiceAvailable}
|
meetingAvailable={voiceAvailable}
|
||||||
onToggleMeeting={() => { void handleToggleMeeting() }}
|
onToggleMeeting={() => { void handleToggleMeeting() }}
|
||||||
|
isSearchOpen={isSearchOpen}
|
||||||
|
isMeetingActionActive={showMeetingPermissions || meetingSummarizing || meetingTranscription.state !== 'idle'}
|
||||||
isBrowserOpen={isBrowserOpen}
|
isBrowserOpen={isBrowserOpen}
|
||||||
onToggleBrowser={handleToggleBrowser}
|
onToggleBrowser={handleToggleBrowser}
|
||||||
isSuggestedTopicsOpen={isSuggestedTopicsOpen}
|
isSuggestedTopicsOpen={isSuggestedTopicsOpen}
|
||||||
|
|
@ -4463,7 +4477,10 @@ function App() {
|
||||||
</ContentHeader>
|
</ContentHeader>
|
||||||
|
|
||||||
{isBrowserOpen ? (
|
{isBrowserOpen ? (
|
||||||
<BrowserPane onClose={handleCloseBrowser} />
|
<BrowserPane
|
||||||
|
onClose={handleCloseBrowser}
|
||||||
|
forceHidden={isSearchOpen || showMeetingPermissions}
|
||||||
|
/>
|
||||||
) : isSuggestedTopicsOpen ? (
|
) : isSuggestedTopicsOpen ? (
|
||||||
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
||||||
<SuggestedTopicsView
|
<SuggestedTopicsView
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ const BLOCKING_OVERLAY_SLOTS = new Set([
|
||||||
|
|
||||||
interface BrowserPaneProps {
|
interface BrowserPaneProps {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
|
forceHidden?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const getActiveTab = (state: BrowserState) =>
|
const getActiveTab = (state: BrowserState) =>
|
||||||
|
|
@ -85,7 +86,7 @@ const getBrowserTabTitle = (tab: BrowserTabState) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BrowserPane({ onClose }: BrowserPaneProps) {
|
export function BrowserPane({ onClose, forceHidden = false }: BrowserPaneProps) {
|
||||||
const [state, setState] = useState<BrowserState>(EMPTY_STATE)
|
const [state, setState] = useState<BrowserState>(EMPTY_STATE)
|
||||||
const [addressValue, setAddressValue] = useState('')
|
const [addressValue, setAddressValue] = useState('')
|
||||||
|
|
||||||
|
|
@ -175,6 +176,12 @@ export function BrowserPane({ onClose }: BrowserPaneProps) {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const syncView = useCallback(() => {
|
const syncView = useCallback(() => {
|
||||||
|
if (forceHidden) {
|
||||||
|
lastBoundsRef.current = null
|
||||||
|
setViewVisible(false)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const doc = viewportRef.current?.ownerDocument
|
const doc = viewportRef.current?.ownerDocument
|
||||||
if (doc && hasBlockingOverlay(doc)) {
|
if (doc && hasBlockingOverlay(doc)) {
|
||||||
lastBoundsRef.current = null
|
lastBoundsRef.current = null
|
||||||
|
|
@ -191,7 +198,7 @@ export function BrowserPane({ onClose }: BrowserPaneProps) {
|
||||||
pushBounds(bounds)
|
pushBounds(bounds)
|
||||||
setViewVisible(true)
|
setViewVisible(true)
|
||||||
return bounds
|
return bounds
|
||||||
}, [measureBounds, pushBounds, setViewVisible])
|
}, [forceHidden, measureBounds, pushBounds, setViewVisible])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
syncView()
|
syncView()
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,8 @@ type SidebarContentPanelProps = {
|
||||||
meetingSummarizing?: boolean
|
meetingSummarizing?: boolean
|
||||||
meetingAvailable?: boolean
|
meetingAvailable?: boolean
|
||||||
onToggleMeeting?: () => void
|
onToggleMeeting?: () => void
|
||||||
|
isSearchOpen?: boolean
|
||||||
|
isMeetingActionActive?: boolean
|
||||||
isBrowserOpen?: boolean
|
isBrowserOpen?: boolean
|
||||||
onToggleBrowser?: () => void
|
onToggleBrowser?: () => void
|
||||||
isSuggestedTopicsOpen?: boolean
|
isSuggestedTopicsOpen?: boolean
|
||||||
|
|
@ -420,6 +422,8 @@ export function SidebarContentPanel({
|
||||||
meetingSummarizing = false,
|
meetingSummarizing = false,
|
||||||
meetingAvailable = false,
|
meetingAvailable = false,
|
||||||
onToggleMeeting,
|
onToggleMeeting,
|
||||||
|
isSearchOpen = false,
|
||||||
|
isMeetingActionActive = false,
|
||||||
isBrowserOpen = false,
|
isBrowserOpen = false,
|
||||||
onToggleBrowser,
|
onToggleBrowser,
|
||||||
isSuggestedTopicsOpen = false,
|
isSuggestedTopicsOpen = false,
|
||||||
|
|
@ -436,6 +440,9 @@ export function SidebarContentPanel({
|
||||||
const [loggingIn, setLoggingIn] = useState(false)
|
const [loggingIn, setLoggingIn] = useState(false)
|
||||||
const [appUrl, setAppUrl] = useState<string | null>(null)
|
const [appUrl, setAppUrl] = useState<string | null>(null)
|
||||||
const { billing } = useBilling(isRowboatConnected)
|
const { billing } = useBilling(isRowboatConnected)
|
||||||
|
const isMeetingQuickActionSelected = isMeetingActionActive
|
||||||
|
const isBrowserQuickActionSelected = isBrowserOpen && !isSearchOpen && !isMeetingQuickActionSelected
|
||||||
|
const isSuggestedTopicsQuickActionSelected = isSuggestedTopicsOpen && !isBrowserOpen
|
||||||
|
|
||||||
const handleRowboatLogin = useCallback(async () => {
|
const handleRowboatLogin = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -533,7 +540,12 @@ export function SidebarContentPanel({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onOpenSearch}
|
onClick={onOpenSearch}
|
||||||
className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground transition-colors"
|
className={cn(
|
||||||
|
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
|
||||||
|
isSearchOpen
|
||||||
|
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
||||||
|
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<SearchIcon className="size-4" />
|
<SearchIcon className="size-4" />
|
||||||
<span>Search</span>
|
<span>Search</span>
|
||||||
|
|
@ -546,9 +558,14 @@ export function SidebarContentPanel({
|
||||||
disabled={meetingState === 'connecting' || meetingState === 'stopping' || meetingSummarizing}
|
disabled={meetingState === 'connecting' || meetingState === 'stopping' || meetingSummarizing}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors disabled:pointer-events-none",
|
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors disabled:pointer-events-none",
|
||||||
|
isMeetingQuickActionSelected
|
||||||
|
? "bg-sidebar-accent"
|
||||||
|
: "hover:bg-sidebar-accent",
|
||||||
meetingState === 'recording'
|
meetingState === 'recording'
|
||||||
? "text-red-500 hover:bg-sidebar-accent"
|
? "text-red-500"
|
||||||
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
: isMeetingQuickActionSelected
|
||||||
|
? "text-sidebar-accent-foreground"
|
||||||
|
: "text-sidebar-foreground/80 hover:text-sidebar-accent-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{meetingSummarizing || meetingState === 'connecting' ? (
|
{meetingSummarizing || meetingState === 'connecting' ? (
|
||||||
|
|
@ -575,7 +592,7 @@ export function SidebarContentPanel({
|
||||||
onClick={onToggleBrowser}
|
onClick={onToggleBrowser}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
|
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
|
||||||
isBrowserOpen
|
isBrowserQuickActionSelected
|
||||||
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
||||||
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
||||||
)}
|
)}
|
||||||
|
|
@ -590,7 +607,7 @@ export function SidebarContentPanel({
|
||||||
onClick={onOpenSuggestedTopics}
|
onClick={onOpenSuggestedTopics}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
|
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
|
||||||
isSuggestedTopicsOpen
|
isSuggestedTopicsQuickActionSelected
|
||||||
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
||||||
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue