Skip to content
Merged
124 changes: 124 additions & 0 deletions packages/views/issues/components/backlog-agent-hint-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"use client";

import { useState } from "react";
import { Archive, ArrowRight, Bot, CheckCircle2 } from "lucide-react";
import {
AlertDialog,
AlertDialogContent,
} from "@multica/ui/components/ui/alert-dialog";
import { Button } from "@multica/ui/components/ui/button";
import { Checkbox } from "@multica/ui/components/ui/checkbox";

interface BacklogAgentHintDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onDismissPermanently: () => void;
onMoveToTodo: () => void;
}

export function BacklogAgentHintDialog({
open,
onOpenChange,
onDismissPermanently,
onMoveToTodo,
}: BacklogAgentHintDialogProps) {
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent className="w-[calc(100vw-2rem)] !max-w-[480px] gap-0 overflow-hidden rounded-lg p-0">
<BacklogAgentHintContent
onKeepInBacklog={() => onOpenChange(false)}
onDismissPermanently={onDismissPermanently}
onMoveToTodo={onMoveToTodo}
/>
</AlertDialogContent>
</AlertDialog>
);
}

interface BacklogAgentHintContentProps {
onKeepInBacklog: () => void;
onDismissPermanently: () => void;
onMoveToTodo: () => void;
}

export function BacklogAgentHintContent({
onKeepInBacklog,
onDismissPermanently,
onMoveToTodo,
}: BacklogAgentHintContentProps) {
const [dontShowAgain, setDontShowAgain] = useState(false);

const handleKeepInBacklog = () => {
if (dontShowAgain) onDismissPermanently();
onKeepInBacklog();
};

const handleMoveToTodo = () => {
if (dontShowAgain) onDismissPermanently();
onMoveToTodo();
};

return (
<>
<div className="px-5 pb-4 pt-5">
<div className="flex items-start gap-3">
<div className="mt-0.5 flex size-10 shrink-0 items-center justify-center rounded-lg border bg-muted text-muted-foreground">
<Bot className="size-4" />
</div>
<div className="min-w-0">
<h2 className="text-base font-semibold">
Agent is paused in Backlog
</h2>
<p className="mt-1 text-sm leading-5 text-muted-foreground">
This issue is parked, so the assigned agent will wait. Move it to
Todo when you want the agent to start.
</p>
</div>
</div>

<div className="mt-4 grid gap-2 rounded-lg border bg-muted/35 p-3 text-sm">
<div className="flex items-center gap-2 text-muted-foreground">
<Archive className="size-4 shrink-0" />
<span className="font-medium text-foreground">Backlog</span>
<span className="text-muted-foreground">keeps the agent paused</span>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
<ArrowRight className="size-4 shrink-0" />
<span className="font-medium text-foreground">Todo</span>
<span className="text-muted-foreground">starts the agent</span>
<CheckCircle2 className="ml-auto size-4 shrink-0 text-primary" />
</div>
</div>
</div>

<div className="border-t bg-muted/25 px-5 py-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<label className="flex min-w-0 cursor-pointer items-center gap-2 text-sm text-muted-foreground">
<Checkbox
checked={dontShowAgain}
onCheckedChange={(next) => setDontShowAgain(next === true)}
/>
<span className="truncate">Don&apos;t show this again</span>
</label>
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
<Button
type="button"
variant="outline"
className="w-full sm:w-auto"
onClick={handleKeepInBacklog}
>
Keep in Backlog
</Button>
<Button
type="button"
className="w-full sm:w-auto"
onClick={handleMoveToTodo}
>
Move to Todo
</Button>
</div>
</div>
</div>
</>
);
}
27 changes: 27 additions & 0 deletions packages/views/issues/components/issue-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import { ProjectPicker } from "../../projects/components/project-picker";
import { CommentCard } from "./comment-card";
import { CommentInput } from "./comment-input";
import { AgentLiveCard, TaskRunHistory } from "./agent-live-card";
import { BacklogAgentHintDialog } from "./backlog-agent-hint-dialog";
import { useQuery } from "@tanstack/react-query";
import { useAuthStore } from "@multica/core/auth";
import { useWorkspaceStore } from "@multica/core/workspace";
Expand Down Expand Up @@ -344,6 +345,7 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
const [sidebarOpen, setSidebarOpen] = useState(defaultSidebarOpen);
const [deleting, setDeleting] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [backlogHintOpen, setBacklogHintOpen] = useState(false);
const [propertiesOpen, setPropertiesOpen] = useState(true);
const [detailsOpen, setDetailsOpen] = useState(true);
const scrollContainerRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -444,6 +446,16 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
{ id, ...updates },
{ onError: () => toast.error("Failed to update issue") },
);
// Hint: assigning an agent to a backlog issue won't trigger execution
// until the issue is moved to an active status.
if (
updates.assignee_type === "agent" &&
updates.assignee_id &&
issue.status === "backlog" &&
localStorage.getItem("multica:backlog-agent-hint-dismissed") !== "true"
) {
setBacklogHintOpen(true);
}
},
[issue, id, updateIssueMutation],
);
Expand Down Expand Up @@ -862,6 +874,21 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
</AlertDialogContent>
</AlertDialog>

<BacklogAgentHintDialog
open={backlogHintOpen}
onOpenChange={setBacklogHintOpen}
onDismissPermanently={() => {
localStorage.setItem("multica:backlog-agent-hint-dismissed", "true");
}}
onMoveToTodo={() => {
updateIssueMutation.mutate(
{ id, status: "todo" },
{ onError: () => toast.error("Failed to update status") },
);
setBacklogHintOpen(false);
}}
/>

{/* Set parent issue picker */}
<IssuePickerDialog
open={parentPickerOpen}
Expand Down
1 change: 1 addition & 0 deletions packages/views/modals/create-issue.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ vi.mock("@multica/core/issues/stores/draft-store", () => ({

vi.mock("@multica/core/issues/mutations", () => ({
useCreateIssue: () => ({ mutateAsync: mockCreateIssue }),
useUpdateIssue: () => ({ mutate: vi.fn() }),
}));

vi.mock("@multica/core/hooks/use-file-upload", () => ({
Expand Down
Loading
Loading