diff --git a/packages/ui/src/components/chat/work-status/DOCUMENTATION.md b/packages/ui/src/components/chat/work-status/DOCUMENTATION.md index 44e96381..18474cae 100644 --- a/packages/ui/src/components/chat/work-status/DOCUMENTATION.md +++ b/packages/ui/src/components/chat/work-status/DOCUMENTATION.md @@ -203,6 +203,11 @@ section decides for itself that it has nothing to say, so they report through `presenceContext.ts` and the panel collapses when none rendered. Deriving that at the panel level would mean duplicating every data source the sections read. +There is one deliberate exception: when the user hides every section, the card +stays visible with a localized empty state and section controls. Collapsing that +state would also hide the only recovery path. A panel with enabled sections but +no data still follows the presence reports and collapses as before. + The scroll offset resets on session change: restoring one session's offset into another's shorter panel lands somewhere arbitrary. diff --git a/packages/ui/src/components/chat/work-status/WorkStatusPanel.tsx b/packages/ui/src/components/chat/work-status/WorkStatusPanel.tsx index 260051a2..3f769d58 100644 --- a/packages/ui/src/components/chat/work-status/WorkStatusPanel.tsx +++ b/packages/ui/src/components/chat/work-status/WorkStatusPanel.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useI18n } from '@/lib/i18n'; import { cn } from '@/lib/utils'; import { ScrollShadow } from '@/components/ui/ScrollShadow'; +import { Button } from '@/components/ui/button'; import { useUIStore } from '@/stores/useUIStore'; import { WORK_STATUS_PANEL_WIDTH } from './useWorkStatusVisibility'; import { WorkStatusGoalRow } from './WorkStatusGoalRow'; @@ -13,7 +14,11 @@ import { WorkStatusMcpSection } from './WorkStatusMcpSection'; import { WorkStatusPinnedSection } from './WorkStatusPinnedSection'; import { WorkStatusContextSection } from './WorkStatusContextSection'; import { WorkStatusSectionsDialog } from './WorkStatusSectionsDialog'; -import { areAllWorkStatusSectionsHidden, isWorkStatusSectionVisible } from './sections'; +import { + areAllWorkStatusSectionsHidden, + getWorkStatusPanelPresentation, + isWorkStatusSectionVisible, +} from './sections'; import { WorkStatusPresenceProvider } from './presence'; import { Icon } from '@/components/icon/Icon'; @@ -89,7 +94,12 @@ export const WorkStatusPanel: React.FC = ({ sessionId, directory, visible // for the transient "no data yet" state so the panel doesn't flash a bare // bordered card on first mount. const allSectionsHidden = areAllWorkStatusSectionsHidden(hiddenSections); - const interactive = visible && (renderedSections > 0 || allSectionsHidden); + const { interactive, showEmptyState } = getWorkStatusPanelPresentation({ + visible, + contentMounted, + renderedSections, + allSectionsHidden, + }); React.useEffect(() => { if (visible) { setContentMounted(true); @@ -251,16 +261,17 @@ export const WorkStatusPanel: React.FC = ({ sessionId, directory, visible ) : null} - {contentMounted && allSectionsHidden ? ( + {showEmptyState ? (
{t('chat.workStatus.sections.allHidden')} - +
) : null} diff --git a/packages/ui/src/components/chat/work-status/WorkStatusSectionsDialog.tsx b/packages/ui/src/components/chat/work-status/WorkStatusSectionsDialog.tsx index ad60fd4d..4bbc81db 100644 --- a/packages/ui/src/components/chat/work-status/WorkStatusSectionsDialog.tsx +++ b/packages/ui/src/components/chat/work-status/WorkStatusSectionsDialog.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useI18n } from '@/lib/i18n'; import { useUIStore } from '@/stores/useUIStore'; import { SettingsCheckboxRow } from '@/components/sections/shared/SettingsSection'; +import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, @@ -63,13 +64,14 @@ export const WorkStatusSectionsDialog: React.FC<{ {noneVisible ? ( {t('chat.workStatus.sections.noneWarning')} ) : } - + ) : null} diff --git a/packages/ui/src/components/chat/work-status/sections.test.ts b/packages/ui/src/components/chat/work-status/sections.test.ts index d4aae75c..9998e46e 100644 --- a/packages/ui/src/components/chat/work-status/sections.test.ts +++ b/packages/ui/src/components/chat/work-status/sections.test.ts @@ -3,6 +3,7 @@ import { WORK_STATUS_SECTION_IDS, WORK_STATUS_SECTION_LABEL_KEYS, areAllWorkStatusSectionsHidden, + getWorkStatusPanelPresentation, isWorkStatusSectionVisible, sanitizeWorkStatusHiddenSections, } from './sections'; @@ -62,6 +63,44 @@ describe('areAllWorkStatusSectionsHidden', () => { }); }); +describe('getWorkStatusPanelPresentation', () => { + test('keeps a visible all-hidden panel interactive and renders its recovery state', () => { + expect(getWorkStatusPanelPresentation({ + visible: true, + contentMounted: true, + renderedSections: 0, + allSectionsHidden: true, + })).toEqual({ interactive: true, showEmptyState: true }); + }); + + test('covers the optimistic fresh-mount count when all sections are hidden', () => { + expect(getWorkStatusPanelPresentation({ + visible: true, + contentMounted: true, + renderedSections: 1, + allSectionsHidden: true, + })).toEqual({ interactive: true, showEmptyState: true }); + }); + + test('preserves collapse when no section has data but sections remain enabled', () => { + expect(getWorkStatusPanelPresentation({ + visible: true, + contentMounted: true, + renderedSections: 0, + allSectionsHidden: false, + })).toEqual({ interactive: false, showEmptyState: false }); + }); + + test('does not expose controls or the empty state during a hidden collapse', () => { + expect(getWorkStatusPanelPresentation({ + visible: false, + contentMounted: false, + renderedSections: 0, + allSectionsHidden: true, + })).toEqual({ interactive: false, showEmptyState: false }); + }); +}); + describe('sanitizeWorkStatusHiddenSections', () => { test('keeps known ids and drops everything else', () => { expect(sanitizeWorkStatusHiddenSections(['usage', 'nope', 42, null, 'tasks'])) diff --git a/packages/ui/src/components/chat/work-status/sections.ts b/packages/ui/src/components/chat/work-status/sections.ts index d7c6fcaf..9b0264cc 100644 --- a/packages/ui/src/components/chat/work-status/sections.ts +++ b/packages/ui/src/components/chat/work-status/sections.ts @@ -60,6 +60,21 @@ export const areAllWorkStatusSectionsHidden = ( ): boolean => hidden != null && WORK_STATUS_SECTION_IDS.every((id) => hidden.includes(id)); +export const getWorkStatusPanelPresentation = ({ + visible, + contentMounted, + renderedSections, + allSectionsHidden, +}: { + visible: boolean; + contentMounted: boolean; + renderedSections: number; + allSectionsHidden: boolean; +}): { interactive: boolean; showEmptyState: boolean } => ({ + interactive: visible && (renderedSections > 0 || allSectionsHidden), + showEmptyState: contentMounted && allSectionsHidden, +}); + export const sanitizeWorkStatusHiddenSections = (value: unknown): WorkStatusSectionId[] => { if (!Array.isArray(value)) return []; const seen = new Set();