diff --git a/src/main/services/error/ErrorTriggerChecker.ts b/src/main/services/error/ErrorTriggerChecker.ts index 9ddbf3de..4887ee6d 100644 --- a/src/main/services/error/ErrorTriggerChecker.ts +++ b/src/main/services/error/ErrorTriggerChecker.ts @@ -185,7 +185,7 @@ export function checkToolResultTrigger( sessionId, projectId, filePath, - projectName: extractProjectName(projectId), + projectName: extractProjectName(projectId, message.cwd), lineNumber, source: result.toolName ?? 'tool_result', message: errorMessage, @@ -220,7 +220,7 @@ export function checkToolResultTrigger( sessionId, projectId, filePath, - projectName: extractProjectName(projectId), + projectName: extractProjectName(projectId, message.cwd), lineNumber, source: trigger.toolName, message: `Tool result matched: ${content.slice(0, 200)}`, @@ -296,7 +296,7 @@ export function checkToolUseTrigger( sessionId, projectId, filePath, - projectName: extractProjectName(projectId), + projectName: extractProjectName(projectId, message.cwd), lineNumber, source: toolUse.name, message: `${trigger.matchField ?? 'tool_use'}: ${fieldValue.slice(0, 200)}`, @@ -443,7 +443,7 @@ export function checkTokenThresholdTrigger( sessionId, projectId, filePath, - projectName: extractProjectName(projectId), + projectName: extractProjectName(projectId, message.cwd), lineNumber, source: toolUse.name, message: tokenMessage, diff --git a/src/main/utils/pathDecoder.ts b/src/main/utils/pathDecoder.ts index fea1038b..7faebb2b 100644 --- a/src/main/utils/pathDecoder.ts +++ b/src/main/utils/pathDecoder.ts @@ -68,7 +68,14 @@ export function decodePath(encodedName: string): string { * @param encodedName - The encoded directory name * @returns The project name */ -export function extractProjectName(encodedName: string): string { +export function extractProjectName(encodedName: string, cwdHint?: string): string { + // Prefer cwdHint (actual filesystem path) since decodePath is lossy for + // paths containing dashes (e.g., "claude-code-context" → "claude/code/context"). + if (cwdHint) { + const segments = cwdHint.split(/[/\\]/).filter(Boolean); + const last = segments[segments.length - 1]; + if (last) return last; + } const decoded = decodePath(encodedName); const segments = decoded.split('/').filter(Boolean); return segments[segments.length - 1] || encodedName; diff --git a/src/renderer/components/notifications/NotificationRow.tsx b/src/renderer/components/notifications/NotificationRow.tsx index 69bf8a4e..950d96f1 100644 --- a/src/renderer/components/notifications/NotificationRow.tsx +++ b/src/renderer/components/notifications/NotificationRow.tsx @@ -74,6 +74,7 @@ export const NotificationRow = ({ style={{ borderColor: 'var(--color-border)', backgroundColor: isHovered ? 'var(--color-surface-raised)' : undefined, + opacity: isUnread ? 1 : 0.5, }} > {/* Color Dot — always visible, opacity indicates read state */} diff --git a/test/main/utils/pathDecoder.test.ts b/test/main/utils/pathDecoder.test.ts index d3e4d178..c7e7d293 100644 --- a/test/main/utils/pathDecoder.test.ts +++ b/test/main/utils/pathDecoder.test.ts @@ -96,6 +96,19 @@ describe('pathDecoder', () => { it('should handle path with underscore in project name', () => { expect(extractProjectName('-Users-username-my_cool_projectname')).toBe('my_cool_projectname'); }); + + it('should prefer cwdHint over lossy decode for dashed project names', () => { + // Without cwdHint, dashes are decoded as slashes (lossy) + expect(extractProjectName('-Users-name-claude-code-context')).toBe('context'); + // With cwdHint, the actual project name is preserved + expect( + extractProjectName('-Users-name-claude-code-context', '/Users/name/claude-code-context') + ).toBe('claude-code-context'); + }); + + it('should fall back to decoded name when cwdHint is undefined', () => { + expect(extractProjectName('-Users-username-projectname', undefined)).toBe('projectname'); + }); }); describe('isValidEncodedPath', () => {