From fd5b1096227365c8f3fe2a5974f2fa095ec8bb15 Mon Sep 17 00:00:00 2001 From: Crabbotix Date: Thu, 19 Feb 2026 08:41:55 +0100 Subject: [PATCH 1/7] Improve remote mobile QoL: add-project flow, refresh resilience, and busy indicators --- src/App.tsx | 9 +- src/features/app/components/Sidebar.test.tsx | 48 +++ src/features/app/components/Sidebar.tsx | 8 + src/features/app/components/WorkspaceCard.tsx | 9 + .../useRemoteThreadLiveConnection.test.tsx | 331 ++++++++++++++++++ .../hooks/useRemoteThreadLiveConnection.ts | 91 ++++- .../hooks/useWorkspaceRefreshOnFocus.test.tsx | 184 ++++++++++ .../hooks/useWorkspaceRefreshOnFocus.ts | 103 ++++-- .../workspaces/hooks/useWorkspaces.test.tsx | 45 +++ .../workspaces/hooks/useWorkspaces.ts | 31 +- src/styles/sidebar.css | 14 + 11 files changed, 844 insertions(+), 29 deletions(-) create mode 100644 src/features/app/hooks/useRemoteThreadLiveConnection.test.tsx create mode 100644 src/features/workspaces/hooks/useWorkspaceRefreshOnFocus.test.tsx diff --git a/src/App.tsx b/src/App.tsx index 9a77f6d7e..aad769434 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -65,7 +65,10 @@ import { useWorkspaceFileListing } from "@app/hooks/useWorkspaceFileListing"; import { useGitBranches } from "@/features/git/hooks/useGitBranches"; import { useBranchSwitcher } from "@/features/git/hooks/useBranchSwitcher"; import { useBranchSwitcherShortcut } from "@/features/git/hooks/useBranchSwitcherShortcut"; -import { useWorkspaceRefreshOnFocus } from "@/features/workspaces/hooks/useWorkspaceRefreshOnFocus"; +import { + REMOTE_WORKSPACE_REFRESH_INTERVAL_MS, + useWorkspaceRefreshOnFocus, +} from "@/features/workspaces/hooks/useWorkspaceRefreshOnFocus"; import { useWorkspaceRestore } from "@/features/workspaces/hooks/useWorkspaceRestore"; import { useRenameWorktreePrompt } from "@/features/workspaces/hooks/useRenameWorktreePrompt"; import { useLayoutController } from "@app/hooks/useLayoutController"; @@ -1591,7 +1594,9 @@ function MainApp() { useWorkspaceRefreshOnFocus({ workspaces, refreshWorkspaces, - listThreadsForWorkspace + listThreadsForWorkspace, + backendMode: appSettings.backendMode, + pollIntervalMs: REMOTE_WORKSPACE_REFRESH_INTERVAL_MS, }); useRemoteThreadRefreshOnFocus({ diff --git a/src/features/app/components/Sidebar.test.tsx b/src/features/app/components/Sidebar.test.tsx index 436e019ad..48515331c 100644 --- a/src/features/app/components/Sidebar.test.tsx +++ b/src/features/app/components/Sidebar.test.tsx @@ -242,4 +242,52 @@ describe("Sidebar", () => { fireEvent.click(draftRow); expect(onSelectWorkspace).toHaveBeenCalledWith("ws-1"); }); + + it("shows a workspace activity indicator when a thread is processing", () => { + render( + , + ); + + const indicator = screen.getByTitle("Streaming updates in progress"); + expect(indicator.className).toContain("workspace-activity-indicator"); + expect(indicator.className).toContain("is-processing"); + }); }); diff --git a/src/features/app/components/Sidebar.tsx b/src/features/app/components/Sidebar.tsx index 0f7cb4465..f2aaa8a5e 100644 --- a/src/features/app/components/Sidebar.tsx +++ b/src/features/app/components/Sidebar.tsx @@ -553,6 +553,13 @@ export const Sidebar = memo(function Sidebar({ isLoadingThreads && threads.length === 0; const isPaging = threadListPagingByWorkspace[entry.id] ?? false; const worktrees = worktreesByParent.get(entry.id) ?? []; + const workspaceIdsWithChildren = [entry.id, ...worktrees.map((child) => child.id)]; + const hasProcessingActivity = workspaceIdsWithChildren.some((workspaceId) => { + const workspaceThreads = threadsByWorkspace[workspaceId] ?? []; + return workspaceThreads.some( + (thread) => threadStatusById[thread.id]?.isProcessing, + ); + }); const addMenuOpen = addMenuAnchor?.workspaceId === entry.id; const isDraftNewAgent = newAgentDraftWorkspaceId === entry.id; const isDraftRowActive = @@ -573,6 +580,7 @@ export const Sidebar = memo(function Sidebar({ isCollapsed={isCollapsed} addMenuOpen={addMenuOpen} addMenuWidth={ADD_MENU_WIDTH} + hasProcessingActivity={hasProcessingActivity} onSelectWorkspace={onSelectWorkspace} onShowWorkspaceMenu={showWorkspaceMenu} onToggleWorkspaceCollapse={onToggleWorkspaceCollapse} diff --git a/src/features/app/components/WorkspaceCard.tsx b/src/features/app/components/WorkspaceCard.tsx index 58eaa86b1..b319eee02 100644 --- a/src/features/app/components/WorkspaceCard.tsx +++ b/src/features/app/components/WorkspaceCard.tsx @@ -9,6 +9,7 @@ type WorkspaceCardProps = { isCollapsed: boolean; addMenuOpen: boolean; addMenuWidth: number; + hasProcessingActivity?: boolean; onSelectWorkspace: (id: string) => void; onShowWorkspaceMenu: (event: MouseEvent, workspaceId: string) => void; onToggleWorkspaceCollapse: (workspaceId: string, collapsed: boolean) => void; @@ -29,6 +30,7 @@ export function WorkspaceCard({ isCollapsed, addMenuOpen, addMenuWidth, + hasProcessingActivity = false, onSelectWorkspace, onShowWorkspaceMenu, onToggleWorkspaceCollapse, @@ -57,6 +59,13 @@ export function WorkspaceCard({
{workspaceName ?? workspace.name} + {hasProcessingActivity && ( + + )}