chore(dev): sync pending branch updates

This commit is contained in:
777genius 2026-04-18 14:12:37 +03:00
parent 5a7d5ea310
commit 8d5ee7d5ab
5 changed files with 2932 additions and 32 deletions

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,7 @@ import {
getNonEmptyTaskCategories,
groupTasksByDate,
groupTasksByProject,
NO_PROJECT_KEY,
sortTasksByFreshness,
} from '@renderer/utils/taskGrouping';
import { deriveTaskDisplayId } from '@shared/utils/taskIdentity';
@ -430,8 +431,18 @@ export const GlobalTaskList = ({
? categories.length > 0
: projectGroups.some((g) => g.tasks.length > 0));
const noProjectGroupColor = useMemo(
() => ({
border: 'var(--color-border)',
glow: 'transparent',
icon: 'var(--color-text-muted)',
text: 'var(--color-text-secondary)',
}),
[]
);
return (
<div className="flex size-full min-w-0 flex-col">
<div className="flex size-full min-w-0 flex-col overflow-x-hidden">
{!hideHeader && (
<div
className="flex shrink-0 items-center gap-2 border-b px-3 py-1.5"
@ -597,7 +608,7 @@ export const GlobalTaskList = ({
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto">
<div className="flex-1 overflow-y-auto overflow-x-hidden">
{globalTasksLoading && !globalTasksInitialized && (
<div className="space-y-2 p-3">
{[1, 2, 3].map((i) => (
@ -644,7 +655,10 @@ export const GlobalTaskList = ({
projectGroups.map((group) => {
if (group.tasks.length === 0) return null;
const isGroupCollapsed = projectCollapsed.isCollapsed(group.projectKey);
const groupColor = projectColor(group.projectLabel);
const isNoProjectGroup = group.projectKey === NO_PROJECT_KEY;
const groupColor = isNoProjectGroup
? noProjectGroupColor
: projectColor(group.projectLabel);
const visibleCount = getProjectGroupVisibleCount(
projectVisibleCountByKey[group.projectKey],
group.tasks.length
@ -661,7 +675,9 @@ export const GlobalTaskList = ({
className="hover:bg-surface-raised/40 sticky top-0 z-10 flex w-full cursor-pointer items-center gap-1.5 p-2 transition-colors"
style={{
backgroundColor: 'var(--color-surface-sidebar)',
backgroundImage: `linear-gradient(90deg, ${groupColor.glow} 0%, transparent 80%)`,
backgroundImage: isNoProjectGroup
? undefined
: `linear-gradient(90deg, ${groupColor.glow} 0%, transparent 80%)`,
boxShadow: `inset 2px 0 0 ${groupColor.border}, inset 0 -1px 0 var(--color-border)`,
}}
>
@ -676,7 +692,7 @@ export const GlobalTaskList = ({
aria-hidden="true"
/>
<span
className="truncate text-[13px] font-bold leading-none"
className="truncate text-[11px] font-bold leading-none"
style={{ color: groupColor.icon }}
>
{group.projectLabel}

View file

@ -51,7 +51,8 @@ export const MemberMessagesTab = ({
const [pagedMessages, setPagedMessages] = useState<InboxMessage[]>([]);
const [nextCursor, setNextCursor] = useState<string | null>(null);
const [hasMore, setHasMore] = useState(false);
const [loading, setLoading] = useState(false);
const [initialPageLoading, setInitialPageLoading] = useState(false);
const [loadingOlderMessages, setLoadingOlderMessages] = useState(false);
const [activityFilter, setActivityFilter] = useState<MemberActivityFilter>(initialFilter);
const [expandedItem, setExpandedItem] = useState<TimelineItem | null>(null);
const { readSet } = useTeamMessagesRead(teamName);
@ -74,7 +75,7 @@ export const MemberMessagesTab = ({
setPagedMessages([]);
setNextCursor(null);
setHasMore(false);
setLoading(true);
setInitialPageLoading(true);
void (async () => {
try {
@ -95,7 +96,9 @@ export const MemberMessagesTab = ({
setHasMore(false);
}
} finally {
if (!cancelled) setLoading(false);
if (!cancelled) {
setInitialPageLoading(false);
}
}
})();
@ -105,8 +108,8 @@ export const MemberMessagesTab = ({
}, [teamName, memberName]);
const loadOlderMessages = useCallback(async () => {
if (!nextCursor || loading) return;
setLoading(true);
if (!nextCursor || loadingOlderMessages) return;
setLoadingOlderMessages(true);
try {
const page = await api.teams.getMessagesPage(teamName, {
beforeTimestamp: nextCursor,
@ -121,9 +124,9 @@ export const MemberMessagesTab = ({
} catch {
// best-effort
} finally {
setLoading(false);
setLoadingOlderMessages(false);
}
}, [teamName, memberName, nextCursor, loading]);
}, [loadingOlderMessages, memberName, nextCursor, teamName]);
const effectiveMessages = useMemo(
() => mergeTeamMessages(messages, pagedMessages),
@ -198,7 +201,7 @@ export const MemberMessagesTab = ({
[onTaskClick, taskMap, tasks]
);
const emptyStateText = loading
const emptyStateText = initialPageLoading
? 'Loading activity...'
: activityFilter === 'comments'
? 'No comments for this member'
@ -289,10 +292,11 @@ export const MemberMessagesTab = ({
variant="ghost"
size="sm"
className="text-xs"
disabled={loading}
aria-busy={loadingOlderMessages}
disabled={loadingOlderMessages}
onClick={() => void loadOlderMessages()}
>
{loading ? 'Loading...' : 'Load older messages'}
Load older messages
</Button>
</div>
)}

View file

@ -152,13 +152,12 @@ export const MessagesPanel = memo(function MessagesPanel({
const [fetchedMessages, setFetchedMessages] = useState<InboxMessage[]>([]);
const [nextCursor, setNextCursor] = useState<string | null>(null);
const [hasMore, setHasMore] = useState(false);
const [messagesLoading, setMessagesLoading] = useState(false);
const [loadingOlderMessages, setLoadingOlderMessages] = useState(false);
const fetchIdRef = useRef(0);
// Initial fetch on mount or team change
useEffect(() => {
const id = ++fetchIdRef.current;
setMessagesLoading(true);
void (async () => {
try {
const page = await api.teams.getMessagesPage(teamName, { limit: PAGE_SIZE });
@ -171,8 +170,6 @@ export const MessagesPanel = memo(function MessagesPanel({
if (fetchIdRef.current === id && messages.length > 0) {
setFetchedMessages(messages);
}
} finally {
if (fetchIdRef.current === id) setMessagesLoading(false);
}
})();
}, [teamName]); // eslint-disable-line react-hooks/exhaustive-deps -- intentionally only on teamName change
@ -193,8 +190,8 @@ export const MessagesPanel = memo(function MessagesPanel({
}, [teamName, isTeamAlive, leadActivity]);
const loadOlderMessages = useCallback(async () => {
if (!nextCursor || messagesLoading) return;
setMessagesLoading(true);
if (!nextCursor || loadingOlderMessages) return;
setLoadingOlderMessages(true);
try {
const page = await api.teams.getMessagesPage(teamName, {
beforeTimestamp: nextCursor,
@ -206,9 +203,9 @@ export const MessagesPanel = memo(function MessagesPanel({
} catch {
// best-effort
} finally {
setMessagesLoading(false);
setLoadingOlderMessages(false);
}
}, [teamName, nextCursor, messagesLoading]);
}, [loadingOlderMessages, nextCursor, teamName]);
// Use fetched messages, fall back to prop messages during initial load
const effectiveMessages = useMemo(() => {
@ -307,7 +304,7 @@ export const MessagesPanel = memo(function MessagesPanel({
for (const [element, setHeight] of observedEntries) {
if (!element) continue;
const updateHeight = () => {
const updateHeight = (): void => {
const nextHeight = Math.ceil(element.getBoundingClientRect().height);
if (nextHeight > 0) {
setHeight(nextHeight);
@ -684,10 +681,11 @@ export const MessagesPanel = memo(function MessagesPanel({
variant="ghost"
size="sm"
className="text-xs text-text-muted"
disabled={messagesLoading}
aria-busy={loadingOlderMessages}
disabled={loadingOlderMessages}
onClick={() => void loadOlderMessages()}
>
{messagesLoading ? 'Loading...' : 'Load older messages'}
Load older messages
</Button>
</div>
)}
@ -869,10 +867,11 @@ export const MessagesPanel = memo(function MessagesPanel({
variant="ghost"
size="sm"
className="text-xs text-text-muted"
disabled={messagesLoading}
aria-busy={loadingOlderMessages}
disabled={loadingOlderMessages}
onClick={() => void loadOlderMessages()}
>
{messagesLoading ? 'Loading...' : 'Load older messages'}
Load older messages
</Button>
</div>
)}
@ -1155,10 +1154,11 @@ export const MessagesPanel = memo(function MessagesPanel({
variant="ghost"
size="sm"
className="text-xs text-text-muted"
disabled={messagesLoading}
aria-busy={loadingOlderMessages}
disabled={loadingOlderMessages}
onClick={() => void loadOlderMessages()}
>
{messagesLoading ? 'Loading...' : 'Load older messages'}
Load older messages
</Button>
</div>
)}

View file

@ -91,8 +91,8 @@ export function getNonEmptyTaskCategories(groups: DateGroupedTasks): DateCategor
return DATE_CATEGORY_ORDER.filter((cat) => groups[cat].length > 0);
}
const NO_PROJECT_KEY = '__no_project__';
const NO_PROJECT_LABEL = 'Without project';
export const NO_PROJECT_KEY = '__no_project__';
export const NO_PROJECT_LABEL = 'No project';
function trimTrailingPathSep(p: string): string {
let s = p;