diff --git a/src/web-ui/src/app/components/NavPanel/sections/sessions/SessionsSectionLayout.test.ts b/src/web-ui/src/app/components/NavPanel/sections/sessions/SessionsSectionLayout.test.ts index cd41f64d1c..a44b4028a2 100644 --- a/src/web-ui/src/app/components/NavPanel/sections/sessions/SessionsSectionLayout.test.ts +++ b/src/web-ui/src/app/components/NavPanel/sections/sessions/SessionsSectionLayout.test.ts @@ -170,6 +170,8 @@ describe('SessionsSection layout styles', () => { const btwBadgeBlock = extractInlineItemBlock(stylesheet, 'btw-badge'); const reviewBadgeBlock = extractInlineItemBlock(stylesheet, 'review-badge'); const backgroundSubagentBadgeBlock = extractInlineItemBlock(stylesheet, 'background-subagent-badge'); + // Shared by session rows and workspace rows, so it must hold its slot width. + const cronBadgeBlock = extractInlineItemBlock(stylesheet, 'cron-badge'); expect(labelBlock).toContain('flex: 1 1 0;'); expect(labelBlock).toContain('overflow: hidden;'); @@ -189,6 +191,10 @@ describe('SessionsSection layout styles', () => { expect(backgroundSubagentBadgeBlock).toContain('line-height: 0;'); expect(backgroundSubagentBadgeBlock).toContain('width: var(--openbitfun-control-icon-size-md);'); expect(backgroundSubagentBadgeBlock).toContain('height: var(--openbitfun-control-icon-size-md);'); + expect(cronBadgeBlock).toContain('flex: 0 0 auto;'); + expect(cronBadgeBlock).toContain('display: inline-flex;'); + expect(cronBadgeBlock).toContain('align-items: center;'); + expect(cronBadgeBlock).toContain('white-space: nowrap;'); const backgroundSubagentIconBlock = extractInlineItemBlock(stylesheet, 'background-subagent-icon'); expect(backgroundSubagentIconBlock).toContain('place-self: center;'); diff --git a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx index 6aff0ef174..f78b8e2b53 100644 --- a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx +++ b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx @@ -117,6 +117,18 @@ const WorkspaceItem: React.FC = ({ getCronJobCountsSnapshot, ); const scheduledJobCount = cronJobCounts.byWorkspaceId.get(workspace.id) ?? 0; + // Rendered through ActionItem's trailing slot so both name rows keep the badge on + // the name line. The rows flatten that label slot with `display: contents`, so a + // badge appended to the label children would silently break if that rule changed. + const scheduledJobBadge = scheduledJobCount > 0 ? ( + + + {scheduledJobCount} + + ) : undefined; const { setActiveWorkspace, closeWorkspaceById, @@ -866,20 +878,12 @@ const WorkspaceItem: React.FC = ({ className="openbitfun-nav-panel__assistant-item-name-action" triggerClassName="openbitfun-nav-panel__assistant-item-name-btn" labelBehavior="static" + metadata={scheduledJobBadge} onClick={e => { e.stopPropagation(); handleCollapseToggle(); }} data-testid="nav-workspace-name-btn" data-workspace-id={workspace.id} > {workspaceDisplayName} - {scheduledJobCount > 0 ? ( - - - {scheduledJobCount} - - ) : null} @@ -1181,6 +1185,7 @@ const WorkspaceItem: React.FC = ({ className="openbitfun-nav-panel__workspace-item-name-action" triggerClassName="openbitfun-nav-panel__workspace-item-name-btn" labelBehavior="static" + metadata={scheduledJobBadge} onClick={e => { e.stopPropagation(); handleCollapseToggle(); }} data-testid="nav-workspace-name-btn" data-workspace-id={workspace.id} diff --git a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceListSectionLayout.test.ts b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceListSectionLayout.test.ts index dc0ad7efd3..d7d50ce78f 100644 --- a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceListSectionLayout.test.ts +++ b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceListSectionLayout.test.ts @@ -159,6 +159,19 @@ describe('WorkspaceListSection layout styles', () => { expect(source.match(/data-testid="nav-workspace-new-session-btn"/g)).toHaveLength(2); }); + it('keeps the scheduled-job badge on the trailing slot of both workspace rows', () => { + const source = readWorkspaceItemSource(); + + // The workspace row and the assistant-group row both flatten their ActionItem + // label slot with `display: contents`, so the badge must stay one flex sibling of + // the name and be rendered by each variant from a single source. + expect(source).toContain('const scheduledJobBadge = scheduledJobCount > 0 ? ('); + expect(source.match(/metadata=\{scheduledJobBadge\}/g)).toHaveLength(2); + expect(source).not.toContain('{scheduledJobCount > 0 ?'); + expect(source).toContain('className="openbitfun-nav-panel__inline-item-cron-badge"'); + expect(source).toContain("title={t('nav.scheduledJobs.badgeTooltip', { count: scheduledJobCount })}"); + }); + it('keeps workspace and assistant rows flat on hover', () => { const stylesheet = readWorkspaceListStylesheet();