From fbf299f276bbf4df0326121044d4d05fbb177b71 Mon Sep 17 00:00:00 2001 From: 777genius Date: Sun, 19 Apr 2026 11:56:53 +0300 Subject: [PATCH] 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. --- package.json | 2 +- .../team/dialogs/EditTeamDialog.tsx | 1 + .../team/members/membersEditorUtils.ts | 40 ++++++++--- .../team/members/membersEditorUtils.test.ts | 71 +++++++++++++++++++ 4 files changed, 105 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 9809f68c..f1a8bb6c 100644 --- a/package.json +++ b/package.json @@ -301,7 +301,7 @@ } ] }, - "packageManager": "pnpm@10.25.0+sha512.5e82639027af37cf832061bcc6d639c219634488e0f2baebe785028a793de7b525ffcd3f7ff574f5e9860654e098fe852ba8ac5dd5cefe1767d23a020a92f501", + "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319", "pnpm": { "onlyBuiltDependencies": [ "electron", diff --git a/src/renderer/components/team/dialogs/EditTeamDialog.tsx b/src/renderer/components/team/dialogs/EditTeamDialog.tsx index 8cf506be..a19e19b4 100644 --- a/src/renderer/components/team/dialogs/EditTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/EditTeamDialog.tsx @@ -164,6 +164,7 @@ export const EditTeamDialog = ({ showJsonEditor={!isTeamAlive} draftKeyPrefix={`editTeam:${teamName}`} projectPath={projectPath ?? null} + existingMembers={currentMembers} lockProviderModel={isTeamAlive} /> diff --git a/src/renderer/components/team/members/membersEditorUtils.ts b/src/renderer/components/team/members/membersEditorUtils.ts index b69843ad..e55e9351 100644 --- a/src/renderer/components/team/members/membersEditorUtils.ts +++ b/src/renderer/components/team/members/membersEditorUtils.ts @@ -2,6 +2,7 @@ import { CUSTOM_ROLE, NO_ROLE, PRESET_ROLES } from '@renderer/constants/teamRole import { serializeChipsWithText } from '@renderer/types/inlineChip'; import { normalizeCreateLaunchProviderForUi } from '@renderer/utils/geminiUiFreeze'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; +import { getMemberColorByName } from '@shared/constants/memberColors'; import { normalizeExplicitTeamModelForUi } from '@renderer/utils/teamModelAvailability'; import { isLeadMember } from '@shared/utils/leadDetection'; import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; @@ -147,19 +148,42 @@ export function buildMemberDraftColorMap( .filter(Boolean) .map((name) => ({ name })); - // When existing members are provided, include them first so their colors - // are reserved and new drafts receive the next available palette entries. - const allEntries = existingMembers ? [...existingMembers, ...draftEntries] : draftEntries; + const existingSeedEntries = (existingMembers ?? []) + .map((member) => ({ + ...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(); + 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 - // they didn't ask for (keeps the API surface unchanged). - if (!existingMembers) return fullMap; + // Mirror the team page color inputs: + // 1. existing members keep their persisted/resolved color + // 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(); 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); } return draftMap; diff --git a/test/renderer/components/team/members/membersEditorUtils.test.ts b/test/renderer/components/team/members/membersEditorUtils.test.ts index defea87b..54b5929b 100644 --- a/test/renderer/components/team/members/membersEditorUtils.test.ts +++ b/test/renderer/components/team/members/membersEditorUtils.test.ts @@ -1,10 +1,14 @@ import { describe, expect, it } from 'vitest'; import { + buildMemberDraftColorMap, buildMembersFromDrafts, + createMemberDraft, createMemberDraftsFromInputs, filterEditableMemberInputs, } from '@renderer/components/team/members/MembersEditorSection'; +import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; +import { getMemberColorByName } from '@shared/constants/memberColors'; import type { ResolvedTeamMember } from '@shared/types'; 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'); + }); });