test: cover work-status empty panel recovery

This commit is contained in:
Bohdan Triapitsyn
2026-08-10 23:31:58 +03:00
parent 02872ff9d3
commit a0207e3857
5 changed files with 83 additions and 11 deletions
@@ -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.
@@ -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<Props> = ({ 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<Props> = ({ sessionId, directory, visible
</WorkStatusPresenceProvider>
) : null}
{contentMounted && allSectionsHidden ? (
{showEmptyState ? (
<div className="flex flex-col items-center justify-center px-4 py-8 text-center">
<span className="text-sm text-muted-foreground">{t('chat.workStatus.sections.allHidden')}</span>
<button
type="button"
<Button
variant="link"
size="xs"
onClick={() => setSectionsDialogOpen(true)}
className="mt-2 text-xs text-muted-foreground underline underline-offset-2 transition-colors hover:text-foreground"
className="mt-2 normal-case text-muted-foreground hover:text-foreground"
>
{t('chat.workStatus.sections.open')}
</button>
</Button>
</div>
) : null}
@@ -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 ? (
<span className="text-xs text-destructive">{t('chat.workStatus.sections.noneWarning')}</span>
) : <span />}
<button
type="button"
<Button
variant="link"
size="xs"
onClick={handleShowAll}
className="text-xs text-muted-foreground underline underline-offset-2 transition-colors hover:text-foreground"
className="normal-case text-muted-foreground hover:text-foreground"
>
{t('chat.workStatus.sections.showAll')}
</button>
</Button>
</div>
) : null}
</DialogContent>
@@ -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']))
@@ -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<WorkStatusSectionId>();