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,
|
||||
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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue