TaskOverflowCleanup.tsx2.9 KBView on GitHub 'use client';
/**
* The tail of a long column, collapsed into one button.
*
* A column with hundreds of long-overdue tasks is not a list anyone scrolls — it's a backlog. So
* past COLUMN_VISIBLE_LIMIT the remainder collapses to a single row that says how much is hidden
* and how stale it is, and offers to hand the whole tail to the agent.
*
* This is the surface for the overdue sweep that TASK_GROUPS_DESIGN deferred: the agent's job is
* to decide, per task, whether the deal is dead (mark it closed-lost), the task is still worth
* doing (execute or reschedule it), or it should simply be dropped.
*
* Clicking seeds a chat message rather than mutating anything directly — bulk-closing deals is
* not something to do without the user reading the proposal first.
*/
import { useMemo } from 'react';
import { Sparkles } from 'lucide-react';
import { useCedarStore } from '@/modules/store';
import type { TaskCardTask } from './TaskKanbanCard';
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
export function TaskOverflowCleanup({
columnLabel,
tasks,
}: {
columnLabel: string;
tasks: TaskCardTask[];
}) {
// `setOverrideInputContent`, not `setChatInputContent`: the latter only writes the store's
// draft mirror, which the mounted composer re-reads on a thread switch and nowhere else — so
// clicking this filled nothing. The override is the path that reaches the editor, and it
// leaves the caret in it.
const setOverrideInputContent = useCedarStore((s) => s.setOverrideInputContent);
const staleCount = useMemo(
() =>
tasks.filter((t) => t.dueDate && Date.now() - new Date(t.dueDate).getTime() > WEEK_MS).length,
[tasks],
);
if (tasks.length === 0) return null;
// Only claim ">1 week overdue" when that's actually what the tail is.
const staleMajority = staleCount > tasks.length / 2;
const prompt =
`I have ${tasks.length} tasks in my "${columnLabel}" lane that are buried below the fold` +
(staleMajority ? `, ${staleCount} of them more than a week overdue` : '') +
`. Go through them and tell me which deals are dead (so we can mark them closed-lost), ` +
`which tasks are still worth doing (and should be rescheduled or executed), and which ` +
`should just be dropped. Show me the proposal before changing anything.\n\n` +
`Task ids: ${tasks.map((t) => t.id).join(', ')}`;
return (
<button
type="button"
onClick={() => setOverrideInputContent(prompt)}
className="bg-action text-action-foreground hover:bg-action-hover w-full rounded-md px-2.5 py-2 text-left text-xs transition-colors"
>
<span className="block font-medium">
+ {tasks.length} {tasks.length === 1 ? 'task' : 'tasks'}
{staleMajority ? ' · more than a week overdue' : ''}
</span>
<span className="mt-0.5 flex items-center gap-1 opacity-90">
<Sparkles className="h-3 w-3 shrink-0" />
Ask AI to clean up — mark closed-lost or run
</span>
</button>
);
}