From 4f1f9e665020e44ddf37fc7e814d5809d09820af Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sat, 5 Sep 2026 14:12:07 +0300 Subject: [PATCH] feat(sidebar): show running project actions by directory --- .../session/sidebar/DOCUMENTATION.md | 20 +++++ .../sidebar/list/SessionProjectCollection.tsx | 2 + .../sidebar/list/SidebarTerminalActivity.tsx | 25 ++++++ .../sidebar/projects/SessionGroupSection.tsx | 2 + .../projects/SessionProjectScroller.tsx | 21 +++-- .../sidebar/projects/sortableItems.tsx | 13 ++- .../DirectoryActionIndicator.test.tsx | 69 +++++++++++++++ .../sessions/DirectoryActionIndicator.tsx | 20 +++++ .../sidebar/sessions/SessionNodeItem.tsx | 30 +++++-- packages/ui/src/lib/api/types.ts | 2 +- packages/ui/src/lib/i18n/messages/de.ts | 1 + packages/ui/src/lib/i18n/messages/en.ts | 1 + packages/ui/src/lib/i18n/messages/es.ts | 1 + packages/ui/src/lib/i18n/messages/fr.ts | 1 + packages/ui/src/lib/i18n/messages/ja.ts | 1 + packages/ui/src/lib/i18n/messages/ko.ts | 1 + packages/ui/src/lib/i18n/messages/pl.ts | 1 + packages/ui/src/lib/i18n/messages/pt-BR.ts | 1 + packages/ui/src/lib/i18n/messages/tr.ts | 1 + packages/ui/src/lib/i18n/messages/uk.ts | 1 + packages/ui/src/lib/i18n/messages/zh-CN.ts | 1 + packages/ui/src/lib/i18n/messages/zh-TW.ts | 1 + packages/ui/src/lib/projectActionTerminal.ts | 13 +++ .../src/lib/terminalSessionObserver.test.ts | 37 +++++++- .../ui/src/lib/terminalSessionObserver.ts | 87 ++++++++++++------- packages/ui/src/stores/DOCUMENTATION.md | 11 ++- 26 files changed, 310 insertions(+), 54 deletions(-) create mode 100644 packages/ui/src/components/session/sidebar/list/SidebarTerminalActivity.tsx create mode 100644 packages/ui/src/components/session/sidebar/sessions/DirectoryActionIndicator.test.tsx create mode 100644 packages/ui/src/components/session/sidebar/sessions/DirectoryActionIndicator.tsx diff --git a/packages/ui/src/components/session/sidebar/DOCUMENTATION.md b/packages/ui/src/components/session/sidebar/DOCUMENTATION.md index 90ece515..a9ec584a 100644 --- a/packages/ui/src/components/session/sidebar/DOCUMENTATION.md +++ b/packages/ui/src/components/session/sidebar/DOCUMENTATION.md @@ -75,3 +75,23 @@ make every row observe unrelated streaming updates. - Directory permission failures remain visible even when stale sessions are retained. Flat groups inspect every represented root/worktree directory; local Desktop may open the native picker for the exact failed directory, while other runtimes keep the ordinary Retry action. - Pins and folder assignments are not pruned from the first startup snapshot or from optimistic mutations. Confirmed local deletion and routed external deletion clean immediately; a later authoritative omission after an established baseline covers missed external delete events. - Pending-permission/question row badges fade with the same hover/menu-open rule as the date label, except on non-VS Code always-visible-actions rows, which reserve permanent padding and keep the badges shown. VS Code hover-reveals its actions over the row's right edge even under `alwaysShowActions`, so its badges keep fading (`selectRowBadgeVisibilityClass` in `sessions/sessionNodeItemUtils.ts`). + + +## Project action indicators + +`SidebarTerminalActivity` shares terminal discovery with the action header and terminal +panel while the sidebar is visible. One server listing covers all directories, including +collapsed projects. It preserves local mutations newer than the listing and keeps known +state on failure. Terminal discovery is separate from OpenCode session bootstrap. + +`DirectoryActionIndicator` reads only its directory's terminal metadata. Output chunks and +unrelated directories do not rerender it. It displays a static `pulse` icon in `status.info` +for live project actions, including auto-discovered commands. Persisted idle tabs and ordinary +interactive terminals do not indicate activity. This indicates process activity, not server +readiness. + +Grouped views show the icon on project-root and worktree headers. Flat project views show +it on the project-root header and on sessions in linked worktrees. Recent shows it on every +session with an active action in its own directory. Archived buckets do not show action +indicators. Indicators stay inside the existing row/header action-padding boundary, so +hover, keyboard focus, and always-visible action buttons move them left without hiding them. diff --git a/packages/ui/src/components/session/sidebar/list/SessionProjectCollection.tsx b/packages/ui/src/components/session/sidebar/list/SessionProjectCollection.tsx index b1788189..8da28f59 100644 --- a/packages/ui/src/components/session/sidebar/list/SessionProjectCollection.tsx +++ b/packages/ui/src/components/session/sidebar/list/SessionProjectCollection.tsx @@ -1,3 +1,4 @@ +import { SidebarTerminalActivity } from './SidebarTerminalActivity'; import React from 'react'; import type { Session } from '@opencode-ai/sdk/v2'; import { useSessionUIStore } from '@/sync/session-ui-store'; @@ -618,6 +619,7 @@ const VisibleSessionProjects: React.FC = ({ topol setSingleProjectId, ]); return <> + { + const { terminal } = useRuntimeAPIs(); + React.useEffect(() => observeTerminalSessions( + terminal, '', + () => new Map(useTerminalStore.getState().actionMutationRevisions), + result => { + const store = useTerminalStore.getState(); + const byDirectory = groupTerminalSessionsByDirectory(result.sessions); + const directories = new Set([...store.sessions.keys(), ...byDirectory.keys()]); + for (const directory of directories) { + store.reconcileServerSessions(directory, byDirectory.get(directory) ?? [], { + startedActionMutationRevisions: result.startedActionMutationRevisions, + }); + } + }, + ), [terminal]); + return null; +}; diff --git a/packages/ui/src/components/session/sidebar/projects/SessionGroupSection.tsx b/packages/ui/src/components/session/sidebar/projects/SessionGroupSection.tsx index abeec157..079b433d 100644 --- a/packages/ui/src/components/session/sidebar/projects/SessionGroupSection.tsx +++ b/packages/ui/src/components/session/sidebar/projects/SessionGroupSection.tsx @@ -1,3 +1,4 @@ +import { DirectoryActionIndicator } from '../sessions/DirectoryActionIndicator'; import React from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; import { useShallow } from 'zustand/react/shallow'; @@ -1197,6 +1198,7 @@ function SessionGroupSectionBase(props: SessionGroupSectionProps): React.ReactNo ) : null} + {!group.isArchivedBucket && group.directory ? : null} {group.isArchivedBucket && allGroupSessions.length > 0 ? (
diff --git a/packages/ui/src/components/session/sidebar/projects/SessionProjectScroller.tsx b/packages/ui/src/components/session/sidebar/projects/SessionProjectScroller.tsx index ab3864ea..3e29f58c 100644 --- a/packages/ui/src/components/session/sidebar/projects/SessionProjectScroller.tsx +++ b/packages/ui/src/components/session/sidebar/projects/SessionProjectScroller.tsx @@ -20,6 +20,7 @@ import { useI18n } from '@/lib/i18n'; import type { ProjectSortOrder } from '@/stores/useSessionDisplayStore'; import { streamPerfCount } from '@/stores/utils/streamDebug'; import { Icon } from '@/components/icon/Icon'; +import { DirectoryActionIndicator } from '../sessions/DirectoryActionIndicator'; type SessionProjectScrollerState = Pick