fix: resolve review bugs and lint errors
- teams.ts: add type validation in handleCreateConfig for displayName/description/color - CreateTaskDialog: scope draft keys per team to prevent cross-team leakage - agentBlocks.ts: replace stateful singleton regex with factory function - teams.test.ts: add missing channel assertions and use os.tmpdir() - SendMessageDialog: move setState out of useEffect to render phase - TeamMemberLogsFinder: remove unused projectId destructuring - MentionableTextarea: add eslint-disable description - useMentionDetection: replace deprecated wordWrap with overflowWrap
This commit is contained in:
parent
704b9cbfe5
commit
42e4b0f4aa
10 changed files with 48 additions and 15 deletions
|
|
@ -690,6 +690,16 @@ async function handleCreateConfig(
|
|||
return { success: false, error: 'members must contain at least one member' };
|
||||
}
|
||||
|
||||
if (payload.displayName !== undefined && typeof payload.displayName !== 'string') {
|
||||
return { success: false, error: 'displayName must be a string' };
|
||||
}
|
||||
if (payload.description !== undefined && typeof payload.description !== 'string') {
|
||||
return { success: false, error: 'description must be a string' };
|
||||
}
|
||||
if (payload.color !== undefined && typeof payload.color !== 'string') {
|
||||
return { success: false, error: 'color must be a string' };
|
||||
}
|
||||
|
||||
const seenNames = new Set<string>();
|
||||
const members: TeamCreateConfigRequest['members'] = [];
|
||||
for (const member of payload.members) {
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export class TeamMemberLogsFinder {
|
|||
const discovery = await this.discoverMemberFiles(teamName, memberName);
|
||||
if (!discovery) return [];
|
||||
|
||||
const { projectDir, projectId, config, sessionIds, knownMembers, isLeadMember } = discovery;
|
||||
const { projectDir, config, sessionIds, knownMembers, isLeadMember } = discovery;
|
||||
const paths: string[] = [];
|
||||
|
||||
if (isLeadMember && config.leadSessionId) {
|
||||
|
|
|
|||
|
|
@ -652,6 +652,7 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
|||
|
||||
<CreateTaskDialog
|
||||
open={createTaskDialog.open}
|
||||
teamName={teamName}
|
||||
members={data.members}
|
||||
tasks={data.tasks}
|
||||
defaultSubject={createTaskDialog.defaultSubject}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
parseStructuredAgentMessage,
|
||||
} from '@renderer/utils/agentMessageFormatting';
|
||||
import { formatAgentRole } from '@renderer/utils/formatAgentRole';
|
||||
import { AGENT_BLOCK_REGEX } from '@shared/constants/agentBlocks';
|
||||
import { createAgentBlockRegex } from '@shared/constants/agentBlocks';
|
||||
import { Bot, ChevronRight, ListPlus, MessageSquare } from 'lucide-react';
|
||||
|
||||
import type { TeamColorSet } from '@renderer/constants/teamColors';
|
||||
|
|
@ -109,7 +109,7 @@ function getSystemMessageLabel(text: string): string | null {
|
|||
|
||||
/** Strip ```info_for_agent ... ``` blocks from text for UI display. */
|
||||
function stripAgentBlocks(text: string): string {
|
||||
return text.replace(AGENT_BLOCK_REGEX, '').trim();
|
||||
return text.replace(createAgentBlockRegex(), '').trim();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import type { ResolvedTeamMember, TeamTask } from '@shared/types';
|
|||
|
||||
interface CreateTaskDialogProps {
|
||||
open: boolean;
|
||||
teamName: string;
|
||||
members: ResolvedTeamMember[];
|
||||
tasks: TeamTask[];
|
||||
defaultSubject?: string;
|
||||
|
|
@ -49,6 +50,7 @@ interface CreateTaskDialogProps {
|
|||
|
||||
export const CreateTaskDialog = ({
|
||||
open,
|
||||
teamName,
|
||||
members,
|
||||
tasks,
|
||||
defaultSubject = '',
|
||||
|
|
@ -60,13 +62,13 @@ export const CreateTaskDialog = ({
|
|||
}: CreateTaskDialogProps): React.JSX.Element => {
|
||||
const [subject, setSubject] = useState(defaultSubject);
|
||||
const descriptionDraft = useDraftPersistence({
|
||||
key: 'createTask:description',
|
||||
key: `createTask:${teamName}:description`,
|
||||
initialValue: defaultDescription || undefined,
|
||||
});
|
||||
const [owner, setOwner] = useState<string>(defaultOwner);
|
||||
const [blockedBy, setBlockedBy] = useState<string[]>([]);
|
||||
const [startImmediately, setStartImmediately] = useState(true);
|
||||
const promptDraft = useDraftPersistence({ key: 'createTask:prompt' });
|
||||
const promptDraft = useDraftPersistence({ key: `createTask:${teamName}:prompt` });
|
||||
const [prevOpen, setPrevOpen] = useState(false);
|
||||
|
||||
if (open && !prevOpen) {
|
||||
|
|
|
|||
|
|
@ -69,19 +69,20 @@ export const SendMessageDialog = ({
|
|||
const [pendingAutoClose, setPendingAutoClose] = useState(false);
|
||||
if (open && lastResult && lastResult !== prevResult) {
|
||||
setPrevResult(lastResult);
|
||||
setMember('');
|
||||
setSummary('');
|
||||
setPendingAutoClose(true);
|
||||
}
|
||||
|
||||
// Side effects (onClose mutates parent state) must run in useEffect, not render phase
|
||||
useEffect(() => {
|
||||
if (pendingAutoClose) {
|
||||
setMember('');
|
||||
textDraft.clearDraft();
|
||||
setSummary('');
|
||||
setPendingAutoClose(false);
|
||||
onClose();
|
||||
}
|
||||
}, [pendingAutoClose]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- only trigger on pendingAutoClose flag
|
||||
}, [pendingAutoClose]);
|
||||
|
||||
const mentionSuggestions = useMemo<MentionSuggestion[]>(
|
||||
() =>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ function parseMentionSegments(text: string, suggestions: MentionSuggestion[]): S
|
|||
// Character after name must be boundary
|
||||
if (end < text.length) {
|
||||
const after = text[end];
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
// eslint-disable-next-line no-useless-escape -- escaped chars needed for regex character class
|
||||
if (!/[\s,.:;!?\)\]\}\-]/.test(after)) continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export function getCaretCoordinates(
|
|||
mirror.style.position = 'absolute';
|
||||
mirror.style.visibility = 'hidden';
|
||||
mirror.style.whiteSpace = 'pre-wrap';
|
||||
mirror.style.wordWrap = 'break-word';
|
||||
mirror.style.overflowWrap = 'break-word';
|
||||
mirror.style.overflow = 'hidden';
|
||||
|
||||
for (const prop of MIRROR_PROPS) {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,21 @@ export const AGENT_BLOCK_OPEN = '```' + AGENT_BLOCK_TAG;
|
|||
export const AGENT_BLOCK_CLOSE = '```';
|
||||
|
||||
/**
|
||||
* Regex that matches a full ``` info_for_agent ... ``` block (including fences).
|
||||
* Regex pattern string for matching ``` info_for_agent ... ``` blocks (including fences).
|
||||
* Supports optional leading/trailing whitespace and newlines around the block.
|
||||
*/
|
||||
export const AGENT_BLOCK_REGEX = new RegExp(
|
||||
'\\n?```' + AGENT_BLOCK_TAG + '\\n[\\s\\S]*?\\n```\\n?',
|
||||
'g'
|
||||
);
|
||||
const AGENT_BLOCK_PATTERN = '\\n?```' + AGENT_BLOCK_TAG + '\\n[\\s\\S]*?\\n```\\n?';
|
||||
|
||||
/**
|
||||
* Creates a new RegExp for matching agent blocks.
|
||||
* Returns a fresh instance each time to avoid stateful 'g' flag issues with .test().
|
||||
*/
|
||||
export function createAgentBlockRegex(): RegExp {
|
||||
return new RegExp(AGENT_BLOCK_PATTERN, 'g');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use createAgentBlockRegex() instead to avoid stateful 'g' flag issues.
|
||||
* Kept for backward compatibility with .replace() calls.
|
||||
*/
|
||||
export const AGENT_BLOCK_REGEX = new RegExp(AGENT_BLOCK_PATTERN, 'g');
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ import {
|
|||
TEAM_PROVISIONING_STATUS,
|
||||
TEAM_REQUEST_REVIEW,
|
||||
TEAM_SEND_MESSAGE,
|
||||
TEAM_GET_ALL_TASKS,
|
||||
TEAM_GET_MEMBER_LOGS,
|
||||
TEAM_GET_MEMBER_STATS,
|
||||
TEAM_START_TASK,
|
||||
TEAM_UPDATE_CONFIG,
|
||||
TEAM_UPDATE_KANBAN,
|
||||
|
|
@ -125,7 +127,9 @@ describe('ipc teams handlers', () => {
|
|||
expect(handlers.has(TEAM_ALIVE_LIST)).toBe(true);
|
||||
expect(handlers.has(TEAM_CREATE_CONFIG)).toBe(true);
|
||||
expect(handlers.has(TEAM_GET_MEMBER_LOGS)).toBe(true);
|
||||
expect(handlers.has(TEAM_GET_MEMBER_STATS)).toBe(true);
|
||||
expect(handlers.has(TEAM_UPDATE_CONFIG)).toBe(true);
|
||||
expect(handlers.has(TEAM_GET_ALL_TASKS)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns success false on invalid sendMessage args', async () => {
|
||||
|
|
@ -289,5 +293,9 @@ describe('ipc teams handlers', () => {
|
|||
expect(handlers.has(TEAM_PROCESS_ALIVE)).toBe(false);
|
||||
expect(handlers.has(TEAM_ALIVE_LIST)).toBe(false);
|
||||
expect(handlers.has(TEAM_CREATE_CONFIG)).toBe(false);
|
||||
expect(handlers.has(TEAM_GET_MEMBER_LOGS)).toBe(false);
|
||||
expect(handlers.has(TEAM_GET_MEMBER_STATS)).toBe(false);
|
||||
expect(handlers.has(TEAM_UPDATE_CONFIG)).toBe(false);
|
||||
expect(handlers.has(TEAM_GET_ALL_TASKS)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue