fix(team): update package manager and enhance member color handling
- Bumped pnpm version to 10.33.0 in package.json. - Added existing members to EditTeamDialog for better context. - Improved buildMemberDraftColorMap to reserve colors for existing members and predict colors for new drafts. - Added tests to ensure color assignment logic works correctly for existing and new members.
This commit is contained in:
parent
b13ab71857
commit
fbf299f276
4 changed files with 105 additions and 9 deletions
|
|
@ -301,7 +301,7 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.25.0+sha512.5e82639027af37cf832061bcc6d639c219634488e0f2baebe785028a793de7b525ffcd3f7ff574f5e9860654e098fe852ba8ac5dd5cefe1767d23a020a92f501",
|
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"electron",
|
"electron",
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,7 @@ export const EditTeamDialog = ({
|
||||||
showJsonEditor={!isTeamAlive}
|
showJsonEditor={!isTeamAlive}
|
||||||
draftKeyPrefix={`editTeam:${teamName}`}
|
draftKeyPrefix={`editTeam:${teamName}`}
|
||||||
projectPath={projectPath ?? null}
|
projectPath={projectPath ?? null}
|
||||||
|
existingMembers={currentMembers}
|
||||||
lockProviderModel={isTeamAlive}
|
lockProviderModel={isTeamAlive}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { CUSTOM_ROLE, NO_ROLE, PRESET_ROLES } from '@renderer/constants/teamRole
|
||||||
import { serializeChipsWithText } from '@renderer/types/inlineChip';
|
import { serializeChipsWithText } from '@renderer/types/inlineChip';
|
||||||
import { normalizeCreateLaunchProviderForUi } from '@renderer/utils/geminiUiFreeze';
|
import { normalizeCreateLaunchProviderForUi } from '@renderer/utils/geminiUiFreeze';
|
||||||
import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
|
import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
|
||||||
|
import { getMemberColorByName } from '@shared/constants/memberColors';
|
||||||
import { normalizeExplicitTeamModelForUi } from '@renderer/utils/teamModelAvailability';
|
import { normalizeExplicitTeamModelForUi } from '@renderer/utils/teamModelAvailability';
|
||||||
import { isLeadMember } from '@shared/utils/leadDetection';
|
import { isLeadMember } from '@shared/utils/leadDetection';
|
||||||
import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
|
import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
|
||||||
|
|
@ -147,19 +148,42 @@ export function buildMemberDraftColorMap(
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((name) => ({ name }));
|
.map((name) => ({ name }));
|
||||||
|
|
||||||
// When existing members are provided, include them first so their colors
|
const existingSeedEntries = (existingMembers ?? [])
|
||||||
// are reserved and new drafts receive the next available palette entries.
|
.map((member) => ({
|
||||||
const allEntries = existingMembers ? [...existingMembers, ...draftEntries] : draftEntries;
|
...member,
|
||||||
|
name: member.name.trim(),
|
||||||
|
color: member.color?.trim() || getMemberColorByName(member.name),
|
||||||
|
}))
|
||||||
|
.filter((member) => member.name);
|
||||||
|
const existingNames = new Set(existingSeedEntries.map((member) => member.name.toLowerCase()));
|
||||||
|
const unseenNewDraftNames = new Set<string>();
|
||||||
|
const uniqueNewDraftEntries = draftEntries.filter((entry) => {
|
||||||
|
const normalizedName = entry.name.toLowerCase();
|
||||||
|
if (existingNames.has(normalizedName) || unseenNewDraftNames.has(normalizedName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
unseenNewDraftNames.add(normalizedName);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
const fullMap = buildMemberColorMap(allEntries);
|
const predictedDraftSeedEntries = uniqueNewDraftEntries.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
color: getMemberColorByName(entry.name),
|
||||||
|
}));
|
||||||
|
|
||||||
// Return only draft entries so callers don't see existing-member keys
|
// Mirror the team page color inputs:
|
||||||
// they didn't ask for (keeps the API surface unchanged).
|
// 1. existing members keep their persisted/resolved color
|
||||||
if (!existingMembers) return fullMap;
|
// 2. new draft members get the same name-based default color that the resolver
|
||||||
|
// will assign after create/launch/add
|
||||||
|
// 3. buildMemberColorMap still resolves rare collisions the same way as the UI
|
||||||
|
const fullMap = buildMemberColorMap([...existingSeedEntries, ...predictedDraftSeedEntries]);
|
||||||
|
const fullColorByName = new Map(
|
||||||
|
Array.from(fullMap.entries()).map(([name, color]) => [name.toLowerCase(), color] as const)
|
||||||
|
);
|
||||||
|
|
||||||
const draftMap = new Map<string, string>();
|
const draftMap = new Map<string, string>();
|
||||||
for (const entry of draftEntries) {
|
for (const entry of draftEntries) {
|
||||||
const color = fullMap.get(entry.name);
|
const color = fullColorByName.get(entry.name.toLowerCase());
|
||||||
if (color) draftMap.set(entry.name, color);
|
if (color) draftMap.set(entry.name, color);
|
||||||
}
|
}
|
||||||
return draftMap;
|
return draftMap;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
buildMemberDraftColorMap,
|
||||||
buildMembersFromDrafts,
|
buildMembersFromDrafts,
|
||||||
|
createMemberDraft,
|
||||||
createMemberDraftsFromInputs,
|
createMemberDraftsFromInputs,
|
||||||
filterEditableMemberInputs,
|
filterEditableMemberInputs,
|
||||||
} from '@renderer/components/team/members/MembersEditorSection';
|
} from '@renderer/components/team/members/MembersEditorSection';
|
||||||
|
import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
|
||||||
|
import { getMemberColorByName } from '@shared/constants/memberColors';
|
||||||
import type { ResolvedTeamMember } from '@shared/types';
|
import type { ResolvedTeamMember } from '@shared/types';
|
||||||
|
|
||||||
describe('members editor editable input filtering', () => {
|
describe('members editor editable input filtering', () => {
|
||||||
|
|
@ -86,4 +90,71 @@ describe('members editor editable input filtering', () => {
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reuses existing member colors for matching draft names', () => {
|
||||||
|
const existingMembers = [{ name: 'alice' }, { name: 'tom' }, { name: 'bob' }];
|
||||||
|
const drafts = existingMembers.map((member) => createMemberDraft({ name: member.name }));
|
||||||
|
|
||||||
|
const expectedColors = buildMemberColorMap(
|
||||||
|
existingMembers.map((member) => ({
|
||||||
|
...member,
|
||||||
|
color: getMemberColorByName(member.name),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
const draftColors = buildMemberDraftColorMap(drafts, existingMembers);
|
||||||
|
|
||||||
|
expect(draftColors.get('alice')).toBe(expectedColors.get('alice'));
|
||||||
|
expect(draftColors.get('tom')).toBe(expectedColors.get('tom'));
|
||||||
|
expect(draftColors.get('bob')).toBe(expectedColors.get('bob'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns new draft members after reserving existing team colors', () => {
|
||||||
|
const existingMembers = [{ name: 'alice' }, { name: 'tom' }];
|
||||||
|
const drafts = [
|
||||||
|
createMemberDraft({ name: 'alice' }),
|
||||||
|
createMemberDraft({ name: 'tom' }),
|
||||||
|
createMemberDraft({ name: 'bob' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
const expectedColors = buildMemberColorMap(
|
||||||
|
[...existingMembers, { name: 'bob' }].map((member) => ({
|
||||||
|
...member,
|
||||||
|
color: getMemberColorByName(member.name),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
const draftColors = buildMemberDraftColorMap(drafts, existingMembers);
|
||||||
|
|
||||||
|
expect(draftColors.get('alice')).toBe(expectedColors.get('alice'));
|
||||||
|
expect(draftColors.get('tom')).toBe(expectedColors.get('tom'));
|
||||||
|
expect(draftColors.get('bob')).toBe(expectedColors.get('bob'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('predicts the same colors as the team page for brand-new draft members', () => {
|
||||||
|
const drafts = ['alice', 'tom', 'bob'].map((name) => createMemberDraft({ name }));
|
||||||
|
|
||||||
|
const expectedColors = buildMemberColorMap(
|
||||||
|
drafts.map((draft) => ({
|
||||||
|
name: draft.name,
|
||||||
|
color: getMemberColorByName(draft.name),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
const draftColors = buildMemberDraftColorMap(drafts);
|
||||||
|
|
||||||
|
expect(draftColors.get('alice')).toBe(expectedColors.get('alice'));
|
||||||
|
expect(draftColors.get('tom')).toBe(expectedColors.get('tom'));
|
||||||
|
expect(draftColors.get('bob')).toBe(expectedColors.get('bob'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves explicit existing colors in edit and launch dialogs', () => {
|
||||||
|
const existingMembers = [
|
||||||
|
{ name: 'alice', color: 'blue' },
|
||||||
|
{ name: 'bob', color: 'pink' },
|
||||||
|
];
|
||||||
|
const drafts = existingMembers.map((member) => createMemberDraft({ name: member.name }));
|
||||||
|
|
||||||
|
const draftColors = buildMemberDraftColorMap(drafts, existingMembers);
|
||||||
|
|
||||||
|
expect(draftColors.get('alice')).toBe('blue');
|
||||||
|
expect(draftColors.get('bob')).toBe('pink');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue