fix(team): keep member launch labels consistent

This commit is contained in:
777genius 2026-04-23 02:22:20 +03:00
parent 0821b182e2
commit e8ebe68576
6 changed files with 49 additions and 8 deletions

View file

@ -126,6 +126,10 @@ export const MemberCard = ({
const spawnCardClass = launchPresentation.cardClass; const spawnCardClass = launchPresentation.cardClass;
const launchVisualState = launchPresentation.launchVisualState; const launchVisualState = launchPresentation.launchVisualState;
const launchStatusLabel = launchPresentation.launchStatusLabel; const launchStatusLabel = launchPresentation.launchStatusLabel;
const displayPresenceLabel =
launchVisualState === 'runtime_pending' || launchVisualState === 'permission_pending'
? (launchStatusLabel ?? presenceLabel)
: presenceLabel;
const colors = getTeamColorSet(memberColor); const colors = getTeamColorSet(memberColor);
const { isLight } = useTheme(); const { isLight } = useTheme();
const pending = taskCounts?.pending ?? 0; const pending = taskCounts?.pending ?? 0;
@ -155,8 +159,7 @@ export const MemberCard = ({
(presenceLabel === 'starting' || (presenceLabel === 'starting' ||
presenceLabel === 'connecting' || presenceLabel === 'connecting' ||
launchVisualState === 'runtime_pending'); launchVisualState === 'runtime_pending');
const launchBadgeLabel = const launchBadgeLabel = presenceLabel === 'starting' ? presenceLabel : displayPresenceLabel;
presenceLabel === 'starting' ? presenceLabel : (launchStatusLabel ?? presenceLabel);
const showRuntimeAdvisoryBadge = const showRuntimeAdvisoryBadge =
!isRemoved && !isRemoved &&
Boolean(runtimeAdvisoryLabel) && Boolean(runtimeAdvisoryLabel) &&
@ -201,7 +204,7 @@ export const MemberCard = ({
</div> </div>
<span <span
className={`absolute -bottom-0.5 -right-0.5 size-2.5 rounded-full border-2 border-[var(--color-surface)] ${dotClass}`} className={`absolute -bottom-0.5 -right-0.5 size-2.5 rounded-full border-2 border-[var(--color-surface)] ${dotClass}`}
aria-label={presenceLabel} aria-label={displayPresenceLabel}
/> />
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
@ -295,7 +298,7 @@ export const MemberCard = ({
variant="secondary" variant="secondary"
className="shrink-0 bg-red-500/15 px-1.5 py-0.5 text-[10px] font-normal leading-none text-red-400" className="shrink-0 bg-red-500/15 px-1.5 py-0.5 text-[10px] font-normal leading-none text-red-400"
> >
{presenceLabel} {displayPresenceLabel}
</Badge> </Badge>
</span> </span>
</TooltipTrigger> </TooltipTrigger>
@ -325,7 +328,7 @@ export const MemberCard = ({
className={`shrink-0 px-1.5 py-0.5 text-[10px] font-normal leading-none ${isRemoved ? 'bg-zinc-600 text-zinc-300' : 'text-[var(--color-text-muted)]'}`} className={`shrink-0 px-1.5 py-0.5 text-[10px] font-normal leading-none ${isRemoved ? 'bg-zinc-600 text-zinc-300' : 'text-[var(--color-text-muted)]'}`}
title={isRemoved ? 'This member has been removed' : activityTitle} title={isRemoved ? 'This member has been removed' : activityTitle}
> >
{isRemoved ? 'removed' : presenceLabel} {isRemoved ? 'removed' : displayPresenceLabel}
</Badge> </Badge>
) : null} ) : null}
{showStartingSkeleton ? ( {showStartingSkeleton ? (

View file

@ -105,7 +105,7 @@ export const MemberDetailHeader = ({
/> />
<span <span
className={`absolute -bottom-0.5 -right-0.5 size-3 rounded-full border-2 border-[var(--color-surface)] ${dotClass}`} className={`absolute -bottom-0.5 -right-0.5 size-3 rounded-full border-2 border-[var(--color-surface)] ${dotClass}`}
aria-label={presenceLabel} aria-label={badgeLabel}
/> />
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">

View file

@ -157,7 +157,7 @@ export const MemberHoverCard = ({
/> />
<span <span
className={`absolute -bottom-0.5 -right-0.5 size-3 rounded-full border-2 border-[var(--color-surface)] ${dotClass}`} className={`absolute -bottom-0.5 -right-0.5 size-3 rounded-full border-2 border-[var(--color-surface)] ${dotClass}`}
aria-label={presenceLabel} aria-label={badgeLabel}
/> />
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">

View file

@ -201,6 +201,41 @@ describe('MemberCard starting-state visuals', () => {
}); });
}); });
it('keeps runtime-pending accessibility copy honest even when launch badge is hidden by an active task', async () => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
await act(async () => {
root.render(
React.createElement(MemberCard, {
member: {
...member,
currentTaskId: currentTask.id,
},
memberColor: 'blue',
currentTask,
isTeamAlive: true,
isTeamProvisioning: false,
spawnStatus: 'online',
spawnLaunchState: 'runtime_pending_bootstrap',
spawnRuntimeAlive: true,
spawnLivenessSource: 'process',
})
);
await Promise.resolve();
});
expect(host.textContent).not.toContain('online');
expect(host.querySelector('[aria-label="connecting"]')).not.toBeNull();
await act(async () => {
root.unmount();
await Promise.resolve();
});
});
it('keeps the starting treatment and runtime summary visible while a runtime is still joining', async () => { it('keeps the starting treatment and runtime summary visible while a runtime is still joining', async () => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
const host = document.createElement('div'); const host = document.createElement('div');
@ -258,7 +293,7 @@ describe('MemberCard starting-state visuals', () => {
}); });
expect(host.textContent).toContain('awaiting permission'); expect(host.textContent).toContain('awaiting permission');
expect(host.querySelector('[aria-label="connecting"]')).not.toBeNull(); expect(host.querySelector('[aria-label="awaiting permission"]')).not.toBeNull();
expect(host.querySelector('.member-waiting-shimmer')).not.toBeNull(); expect(host.querySelector('.member-waiting-shimmer')).not.toBeNull();
await act(async () => { await act(async () => {

View file

@ -123,6 +123,7 @@ describe('MemberDetailHeader spawn-aware presence', () => {
expect(host.textContent).toContain('connecting'); expect(host.textContent).toContain('connecting');
expect(host.textContent).not.toContain('online'); expect(host.textContent).not.toContain('online');
expect(host.querySelector('[aria-label="connecting"]')).not.toBeNull();
await act(async () => { await act(async () => {
root.unmount(); root.unmount();

View file

@ -190,6 +190,7 @@ describe('MemberHoverCard spawn-aware presence', () => {
expect(host.textContent).toContain('connecting'); expect(host.textContent).toContain('connecting');
expect(host.textContent).not.toContain('online'); expect(host.textContent).not.toContain('online');
expect(host.querySelector('[aria-label="connecting"]')).not.toBeNull();
await act(async () => { await act(async () => {
root.unmount(); root.unmount();
@ -225,6 +226,7 @@ describe('MemberHoverCard spawn-aware presence', () => {
expect(host.textContent).toContain('connecting'); expect(host.textContent).toContain('connecting');
expect(host.textContent).not.toContain('online'); expect(host.textContent).not.toContain('online');
expect(host.querySelector('[aria-label="connecting"]')).not.toBeNull();
await act(async () => { await act(async () => {
root.unmount(); root.unmount();