diff --git a/src/renderer/components/settings/components/SettingRow.tsx b/src/renderer/components/settings/components/SettingRow.tsx
index d0353e34..a9a4ec3c 100644
--- a/src/renderer/components/settings/components/SettingRow.tsx
+++ b/src/renderer/components/settings/components/SettingRow.tsx
@@ -1,17 +1,19 @@
/**
* SettingRow - Setting row component for consistent layout.
- * Linear-style clean row without icons.
+ * Linear-style clean row with optional icon.
*/
interface SettingRowProps {
readonly label: string;
readonly description?: string;
+ readonly icon?: React.ReactNode;
readonly children: React.ReactNode;
}
export const SettingRow = ({
label,
description,
+ icon,
children,
}: SettingRowProps): React.JSX.Element => {
return (
@@ -19,15 +21,22 @@ export const SettingRow = ({
className="flex items-center justify-between border-b py-3"
style={{ borderColor: 'var(--color-border-subtle)' }}
>
-
-
- {label}
-
- {description && (
-
- {description}
+
+ {icon ? (
+
+ {icon}
- )}
+ ) : null}
+
+
+ {label}
+
+ {description && (
+
+ {description}
+
+ )}
+
{children}
diff --git a/src/renderer/components/settings/components/SettingsSectionHeader.tsx b/src/renderer/components/settings/components/SettingsSectionHeader.tsx
index 67887fd9..e94a0616 100644
--- a/src/renderer/components/settings/components/SettingsSectionHeader.tsx
+++ b/src/renderer/components/settings/components/SettingsSectionHeader.tsx
@@ -1,18 +1,23 @@
/**
* SettingsSectionHeader - Section header component.
- * Linear-style subtle label.
+ * Linear-style subtle label with optional icon.
*/
interface SettingsSectionHeaderProps {
readonly title: string;
+ readonly icon?: React.ReactNode;
}
-export const SettingsSectionHeader = ({ title }: SettingsSectionHeaderProps): React.JSX.Element => {
+export const SettingsSectionHeader = ({
+ title,
+ icon,
+}: SettingsSectionHeaderProps): React.JSX.Element => {
return (
+ {icon}
{title}
);
diff --git a/src/renderer/components/settings/sections/NotificationsSection.tsx b/src/renderer/components/settings/sections/NotificationsSection.tsx
index b75c4986..898af92a 100644
--- a/src/renderer/components/settings/sections/NotificationsSection.tsx
+++ b/src/renderer/components/settings/sections/NotificationsSection.tsx
@@ -7,7 +7,21 @@ import {
RepositoryDropdown,
SelectedRepositoryItem,
} from '@renderer/components/common/RepositoryDropdown';
-import { ExternalLink } from 'lucide-react';
+import {
+ AlertTriangle,
+ ArrowRightLeft,
+ Bell,
+ BellRing,
+ Clock,
+ ExternalLink,
+ EyeOff,
+ HelpCircle,
+ Inbox,
+ Mail,
+ MessageSquare,
+ PartyPopper,
+ Volume2,
+} from 'lucide-react';
import { SettingRow, SettingsSectionHeader, SettingsSelect, SettingsToggle } from '../components';
import { NotificationTriggerSettings } from '../NotificationTriggerSettings';
@@ -80,7 +94,10 @@ export const NotificationsSection = ({
return (
{/* Task Completion Notifications */}
-
+
}
+ />
{/* Notification Settings */}
-
+
} />
}
>
-
+ }
+ >
onNotificationToggle('soundEnabled', v)}
@@ -129,6 +151,7 @@ export const NotificationsSection = ({
}
>
}
>
}
>
}
>
}
>
}
>
{isSnoozed ? (
@@ -208,12 +236,17 @@ export const NotificationsSection = ({
{/* Task Status Change Notifications — grouped section */}
-
-
- Task status change notifications
+
+
-
- Show native OS notifications when a task's status changes
+
+
+ Task status change notifications
+
+
+ Show native OS notifications when a task's status changes
+
@@ -282,7 +315,7 @@ export const NotificationsSection = ({
onRemoveTrigger={onRemoveTrigger}
/>
-
+
} />
Notifications from these repositories will be ignored
diff --git a/src/renderer/components/team/TeamDetailView.tsx b/src/renderer/components/team/TeamDetailView.tsx
index 42f36a8d..fd65d402 100644
--- a/src/renderer/components/team/TeamDetailView.tsx
+++ b/src/renderer/components/team/TeamDetailView.tsx
@@ -1581,8 +1581,14 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
const el = document.querySelector(`[data-task-id="${taskId}"]`);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
- el.classList.add('ring-2', 'ring-blue-400/50');
- setTimeout(() => el.classList.remove('ring-2', 'ring-blue-400/50'), 1500);
+ el.classList.remove('kanban-card-focus-pulse');
+ void (el as HTMLElement).offsetWidth;
+ el.classList.add('kanban-card-focus-pulse');
+ el.addEventListener(
+ 'animationend',
+ () => el.classList.remove('kanban-card-focus-pulse'),
+ { once: true }
+ );
}
}}
onTaskClick={(task) => setSelectedTask(task)}
@@ -1919,8 +1925,14 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
const el = document.querySelector(`[data-task-id="${taskId}"]`);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
- el.classList.add('ring-2', 'ring-blue-400/50');
- setTimeout(() => el.classList.remove('ring-2', 'ring-blue-400/50'), 1500);
+ el.classList.remove('kanban-card-focus-pulse');
+ void (el as HTMLElement).offsetWidth;
+ el.classList.add('kanban-card-focus-pulse');
+ el.addEventListener(
+ 'animationend',
+ () => el.classList.remove('kanban-card-focus-pulse'),
+ { once: true }
+ );
}
}}
onOwnerChange={(taskId, owner) => {
diff --git a/src/renderer/constants/teamColors.ts b/src/renderer/constants/teamColors.ts
index 6b55b3ac..e37ff909 100644
--- a/src/renderer/constants/teamColors.ts
+++ b/src/renderer/constants/teamColors.ts
@@ -89,9 +89,9 @@ const TEAMMATE_COLORS: Record
= {
/** Reserved for the human user — never assigned to team members. */
user: {
border: '#f5f5f4',
- borderLight: '#a8a29e',
+ borderLight: '#78716c',
badge: 'rgba(245, 245, 244, 0.12)',
- badgeLight: 'rgba(120, 113, 108, 0.14)',
+ badgeLight: 'rgba(87, 83, 78, 0.18)',
text: '#d6d3d1',
textLight: '#44403c',
},
diff --git a/src/renderer/index.css b/src/renderer/index.css
index c49636dc..d93b5d46 100644
--- a/src/renderer/index.css
+++ b/src/renderer/index.css
@@ -295,6 +295,43 @@
}
}
+@keyframes kanban-focus-pulse {
+ 0% {
+ box-shadow: 0 0 0 0 rgba(96, 165, 250, 0.6);
+ background-color: rgba(96, 165, 250, 0.15);
+ }
+ 14% {
+ box-shadow: 0 0 16px 6px rgba(96, 165, 250, 0.5);
+ background-color: rgba(96, 165, 250, 0.12);
+ }
+ 28% {
+ box-shadow: 0 0 0 0 rgba(96, 165, 250, 0.1);
+ background-color: rgba(96, 165, 250, 0.02);
+ }
+ 42% {
+ box-shadow: 0 0 14px 5px rgba(96, 165, 250, 0.45);
+ background-color: rgba(96, 165, 250, 0.1);
+ }
+ 56% {
+ box-shadow: 0 0 0 0 rgba(96, 165, 250, 0.1);
+ background-color: rgba(96, 165, 250, 0.02);
+ }
+ 70% {
+ box-shadow: 0 0 10px 4px rgba(96, 165, 250, 0.3);
+ background-color: rgba(96, 165, 250, 0.06);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 rgba(96, 165, 250, 0);
+ background-color: transparent;
+ }
+}
+
+.kanban-card-focus-pulse {
+ animation: kanban-focus-pulse 2.2s ease-out;
+ outline: 2px solid rgba(96, 165, 250, 0.6);
+ outline-offset: -1px;
+}
+
.kanban-grid-resize-handle-n,
.kanban-grid-resize-handle-s {
left: 50%;