chore(dev): sync pending branch updates
This commit is contained in:
parent
5a7d5ea310
commit
8d5ee7d5ab
5 changed files with 2932 additions and 32 deletions
2880
docs/extensions/plugin-kit-ai-integration-plan.md
Normal file
2880
docs/extensions/plugin-kit-ai-integration-plan.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -12,6 +12,7 @@ import {
|
||||||
getNonEmptyTaskCategories,
|
getNonEmptyTaskCategories,
|
||||||
groupTasksByDate,
|
groupTasksByDate,
|
||||||
groupTasksByProject,
|
groupTasksByProject,
|
||||||
|
NO_PROJECT_KEY,
|
||||||
sortTasksByFreshness,
|
sortTasksByFreshness,
|
||||||
} from '@renderer/utils/taskGrouping';
|
} from '@renderer/utils/taskGrouping';
|
||||||
import { deriveTaskDisplayId } from '@shared/utils/taskIdentity';
|
import { deriveTaskDisplayId } from '@shared/utils/taskIdentity';
|
||||||
|
|
@ -430,8 +431,18 @@ export const GlobalTaskList = ({
|
||||||
? categories.length > 0
|
? categories.length > 0
|
||||||
: projectGroups.some((g) => g.tasks.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 (
|
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 && (
|
{!hideHeader && (
|
||||||
<div
|
<div
|
||||||
className="flex shrink-0 items-center gap-2 border-b px-3 py-1.5"
|
className="flex shrink-0 items-center gap-2 border-b px-3 py-1.5"
|
||||||
|
|
@ -597,7 +608,7 @@ export const GlobalTaskList = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto overflow-x-hidden">
|
||||||
{globalTasksLoading && !globalTasksInitialized && (
|
{globalTasksLoading && !globalTasksInitialized && (
|
||||||
<div className="space-y-2 p-3">
|
<div className="space-y-2 p-3">
|
||||||
{[1, 2, 3].map((i) => (
|
{[1, 2, 3].map((i) => (
|
||||||
|
|
@ -644,7 +655,10 @@ export const GlobalTaskList = ({
|
||||||
projectGroups.map((group) => {
|
projectGroups.map((group) => {
|
||||||
if (group.tasks.length === 0) return null;
|
if (group.tasks.length === 0) return null;
|
||||||
const isGroupCollapsed = projectCollapsed.isCollapsed(group.projectKey);
|
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(
|
const visibleCount = getProjectGroupVisibleCount(
|
||||||
projectVisibleCountByKey[group.projectKey],
|
projectVisibleCountByKey[group.projectKey],
|
||||||
group.tasks.length
|
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"
|
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={{
|
style={{
|
||||||
backgroundColor: 'var(--color-surface-sidebar)',
|
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)`,
|
boxShadow: `inset 2px 0 0 ${groupColor.border}, inset 0 -1px 0 var(--color-border)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -676,7 +692,7 @@ export const GlobalTaskList = ({
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className="truncate text-[13px] font-bold leading-none"
|
className="truncate text-[11px] font-bold leading-none"
|
||||||
style={{ color: groupColor.icon }}
|
style={{ color: groupColor.icon }}
|
||||||
>
|
>
|
||||||
{group.projectLabel}
|
{group.projectLabel}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,8 @@ export const MemberMessagesTab = ({
|
||||||
const [pagedMessages, setPagedMessages] = useState<InboxMessage[]>([]);
|
const [pagedMessages, setPagedMessages] = useState<InboxMessage[]>([]);
|
||||||
const [nextCursor, setNextCursor] = useState<string | null>(null);
|
const [nextCursor, setNextCursor] = useState<string | null>(null);
|
||||||
const [hasMore, setHasMore] = useState(false);
|
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 [activityFilter, setActivityFilter] = useState<MemberActivityFilter>(initialFilter);
|
||||||
const [expandedItem, setExpandedItem] = useState<TimelineItem | null>(null);
|
const [expandedItem, setExpandedItem] = useState<TimelineItem | null>(null);
|
||||||
const { readSet } = useTeamMessagesRead(teamName);
|
const { readSet } = useTeamMessagesRead(teamName);
|
||||||
|
|
@ -74,7 +75,7 @@ export const MemberMessagesTab = ({
|
||||||
setPagedMessages([]);
|
setPagedMessages([]);
|
||||||
setNextCursor(null);
|
setNextCursor(null);
|
||||||
setHasMore(false);
|
setHasMore(false);
|
||||||
setLoading(true);
|
setInitialPageLoading(true);
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -95,7 +96,9 @@ export const MemberMessagesTab = ({
|
||||||
setHasMore(false);
|
setHasMore(false);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (!cancelled) setLoading(false);
|
if (!cancelled) {
|
||||||
|
setInitialPageLoading(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
@ -105,8 +108,8 @@ export const MemberMessagesTab = ({
|
||||||
}, [teamName, memberName]);
|
}, [teamName, memberName]);
|
||||||
|
|
||||||
const loadOlderMessages = useCallback(async () => {
|
const loadOlderMessages = useCallback(async () => {
|
||||||
if (!nextCursor || loading) return;
|
if (!nextCursor || loadingOlderMessages) return;
|
||||||
setLoading(true);
|
setLoadingOlderMessages(true);
|
||||||
try {
|
try {
|
||||||
const page = await api.teams.getMessagesPage(teamName, {
|
const page = await api.teams.getMessagesPage(teamName, {
|
||||||
beforeTimestamp: nextCursor,
|
beforeTimestamp: nextCursor,
|
||||||
|
|
@ -121,9 +124,9 @@ export const MemberMessagesTab = ({
|
||||||
} catch {
|
} catch {
|
||||||
// best-effort
|
// best-effort
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoadingOlderMessages(false);
|
||||||
}
|
}
|
||||||
}, [teamName, memberName, nextCursor, loading]);
|
}, [loadingOlderMessages, memberName, nextCursor, teamName]);
|
||||||
|
|
||||||
const effectiveMessages = useMemo(
|
const effectiveMessages = useMemo(
|
||||||
() => mergeTeamMessages(messages, pagedMessages),
|
() => mergeTeamMessages(messages, pagedMessages),
|
||||||
|
|
@ -198,7 +201,7 @@ export const MemberMessagesTab = ({
|
||||||
[onTaskClick, taskMap, tasks]
|
[onTaskClick, taskMap, tasks]
|
||||||
);
|
);
|
||||||
|
|
||||||
const emptyStateText = loading
|
const emptyStateText = initialPageLoading
|
||||||
? 'Loading activity...'
|
? 'Loading activity...'
|
||||||
: activityFilter === 'comments'
|
: activityFilter === 'comments'
|
||||||
? 'No comments for this member'
|
? 'No comments for this member'
|
||||||
|
|
@ -289,10 +292,11 @@ export const MemberMessagesTab = ({
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-xs"
|
className="text-xs"
|
||||||
disabled={loading}
|
aria-busy={loadingOlderMessages}
|
||||||
|
disabled={loadingOlderMessages}
|
||||||
onClick={() => void loadOlderMessages()}
|
onClick={() => void loadOlderMessages()}
|
||||||
>
|
>
|
||||||
{loading ? 'Loading...' : 'Load older messages'}
|
Load older messages
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -152,13 +152,12 @@ export const MessagesPanel = memo(function MessagesPanel({
|
||||||
const [fetchedMessages, setFetchedMessages] = useState<InboxMessage[]>([]);
|
const [fetchedMessages, setFetchedMessages] = useState<InboxMessage[]>([]);
|
||||||
const [nextCursor, setNextCursor] = useState<string | null>(null);
|
const [nextCursor, setNextCursor] = useState<string | null>(null);
|
||||||
const [hasMore, setHasMore] = useState(false);
|
const [hasMore, setHasMore] = useState(false);
|
||||||
const [messagesLoading, setMessagesLoading] = useState(false);
|
const [loadingOlderMessages, setLoadingOlderMessages] = useState(false);
|
||||||
const fetchIdRef = useRef(0);
|
const fetchIdRef = useRef(0);
|
||||||
|
|
||||||
// Initial fetch on mount or team change
|
// Initial fetch on mount or team change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = ++fetchIdRef.current;
|
const id = ++fetchIdRef.current;
|
||||||
setMessagesLoading(true);
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const page = await api.teams.getMessagesPage(teamName, { limit: PAGE_SIZE });
|
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) {
|
if (fetchIdRef.current === id && messages.length > 0) {
|
||||||
setFetchedMessages(messages);
|
setFetchedMessages(messages);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
if (fetchIdRef.current === id) setMessagesLoading(false);
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [teamName]); // eslint-disable-line react-hooks/exhaustive-deps -- intentionally only on teamName change
|
}, [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]);
|
}, [teamName, isTeamAlive, leadActivity]);
|
||||||
|
|
||||||
const loadOlderMessages = useCallback(async () => {
|
const loadOlderMessages = useCallback(async () => {
|
||||||
if (!nextCursor || messagesLoading) return;
|
if (!nextCursor || loadingOlderMessages) return;
|
||||||
setMessagesLoading(true);
|
setLoadingOlderMessages(true);
|
||||||
try {
|
try {
|
||||||
const page = await api.teams.getMessagesPage(teamName, {
|
const page = await api.teams.getMessagesPage(teamName, {
|
||||||
beforeTimestamp: nextCursor,
|
beforeTimestamp: nextCursor,
|
||||||
|
|
@ -206,9 +203,9 @@ export const MessagesPanel = memo(function MessagesPanel({
|
||||||
} catch {
|
} catch {
|
||||||
// best-effort
|
// best-effort
|
||||||
} finally {
|
} finally {
|
||||||
setMessagesLoading(false);
|
setLoadingOlderMessages(false);
|
||||||
}
|
}
|
||||||
}, [teamName, nextCursor, messagesLoading]);
|
}, [loadingOlderMessages, nextCursor, teamName]);
|
||||||
|
|
||||||
// Use fetched messages, fall back to prop messages during initial load
|
// Use fetched messages, fall back to prop messages during initial load
|
||||||
const effectiveMessages = useMemo(() => {
|
const effectiveMessages = useMemo(() => {
|
||||||
|
|
@ -307,7 +304,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
||||||
for (const [element, setHeight] of observedEntries) {
|
for (const [element, setHeight] of observedEntries) {
|
||||||
if (!element) continue;
|
if (!element) continue;
|
||||||
|
|
||||||
const updateHeight = () => {
|
const updateHeight = (): void => {
|
||||||
const nextHeight = Math.ceil(element.getBoundingClientRect().height);
|
const nextHeight = Math.ceil(element.getBoundingClientRect().height);
|
||||||
if (nextHeight > 0) {
|
if (nextHeight > 0) {
|
||||||
setHeight(nextHeight);
|
setHeight(nextHeight);
|
||||||
|
|
@ -684,10 +681,11 @@ export const MessagesPanel = memo(function MessagesPanel({
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-xs text-text-muted"
|
className="text-xs text-text-muted"
|
||||||
disabled={messagesLoading}
|
aria-busy={loadingOlderMessages}
|
||||||
|
disabled={loadingOlderMessages}
|
||||||
onClick={() => void loadOlderMessages()}
|
onClick={() => void loadOlderMessages()}
|
||||||
>
|
>
|
||||||
{messagesLoading ? 'Loading...' : 'Load older messages'}
|
Load older messages
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -869,10 +867,11 @@ export const MessagesPanel = memo(function MessagesPanel({
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-xs text-text-muted"
|
className="text-xs text-text-muted"
|
||||||
disabled={messagesLoading}
|
aria-busy={loadingOlderMessages}
|
||||||
|
disabled={loadingOlderMessages}
|
||||||
onClick={() => void loadOlderMessages()}
|
onClick={() => void loadOlderMessages()}
|
||||||
>
|
>
|
||||||
{messagesLoading ? 'Loading...' : 'Load older messages'}
|
Load older messages
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -1155,10 +1154,11 @@ export const MessagesPanel = memo(function MessagesPanel({
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-xs text-text-muted"
|
className="text-xs text-text-muted"
|
||||||
disabled={messagesLoading}
|
aria-busy={loadingOlderMessages}
|
||||||
|
disabled={loadingOlderMessages}
|
||||||
onClick={() => void loadOlderMessages()}
|
onClick={() => void loadOlderMessages()}
|
||||||
>
|
>
|
||||||
{messagesLoading ? 'Loading...' : 'Load older messages'}
|
Load older messages
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,8 @@ export function getNonEmptyTaskCategories(groups: DateGroupedTasks): DateCategor
|
||||||
return DATE_CATEGORY_ORDER.filter((cat) => groups[cat].length > 0);
|
return DATE_CATEGORY_ORDER.filter((cat) => groups[cat].length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const NO_PROJECT_KEY = '__no_project__';
|
export const NO_PROJECT_KEY = '__no_project__';
|
||||||
const NO_PROJECT_LABEL = 'Without project';
|
export const NO_PROJECT_LABEL = 'No project';
|
||||||
|
|
||||||
function trimTrailingPathSep(p: string): string {
|
function trimTrailingPathSep(p: string): string {
|
||||||
let s = p;
|
let s = p;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue