fix(ui): hide planning chip on standard issues and surface toggle via three-dot menu (PAP-3613)

Per user feedback: only show the Planning chip in the composer when planning
mode is in effect. On a standard issue we no longer render the chip; instead
a three-dot button next to the paperclip opens a menu with "Switch to
planning" (or "Switch to standard" once toggled). The container only goes
amber when pendingWorkMode === planning, matching the visible chip.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta
2026-05-05 12:20:46 -05:00
parent e745742110
commit f8f82b7ec2
2 changed files with 70 additions and 28 deletions

View File

@@ -383,7 +383,7 @@ describe("IssueChatThread", () => {
});
});
it("toggles the composer work mode for the next submission without changing the issue immediately", () => {
it("hides the planning chip on a standard issue and exposes the toggle through the menu", () => {
const root = createRoot(container);
const onWorkModeChange = vi.fn();
@@ -404,21 +404,41 @@ describe("IssueChatThread", () => {
);
});
const toggle = container.querySelector(
'[data-testid="issue-chat-composer-work-mode-toggle"]',
expect(
container.querySelector('[data-testid="issue-chat-composer-work-mode-toggle"]'),
).toBeNull();
const composer = container.querySelector('[data-testid="issue-chat-composer"]');
expect(composer?.getAttribute("data-pending-work-mode")).toBe("standard");
expect(composer?.className).not.toContain("amber");
const menuTrigger = container.querySelector(
'[data-testid="issue-chat-composer-work-mode-menu"]',
) as HTMLButtonElement | null;
expect(toggle).not.toBeNull();
expect(toggle?.getAttribute("data-pending-work-mode")).toBe("standard");
expect(menuTrigger).not.toBeNull();
act(() => {
menuTrigger?.click();
});
const menuItem = document.querySelector(
'[data-testid="issue-chat-composer-work-mode-menu-toggle"]',
) as HTMLButtonElement | null;
expect(menuItem).not.toBeNull();
expect(menuItem?.textContent).toContain("Switch to planning");
act(() => {
toggle?.click();
menuItem?.click();
});
expect(onWorkModeChange).not.toHaveBeenCalled();
const composer = container.querySelector('[data-testid="issue-chat-composer"]');
expect(composer?.getAttribute("data-pending-work-mode")).toBe("planning");
expect(composer?.className).toContain("amber");
const visibleChip = container.querySelector(
'[data-testid="issue-chat-composer-work-mode-toggle"]',
);
expect(visibleChip).not.toBeNull();
expect(visibleChip?.textContent).toContain("Planning");
act(() => {
root.unmount();
});

View File

@@ -2837,6 +2837,7 @@ const IssueChatComposer = forwardRef<IssueChatComposerHandle, IssueChatComposerP
const [unassignedConfirmed, setUnassignedConfirmed] = useState(false);
const resolvedIssueWorkMode: IssueWorkMode = issueWorkMode ?? "standard";
const [pendingWorkMode, setPendingWorkMode] = useState<IssueWorkMode>(resolvedIssueWorkMode);
const [workModeMenuOpen, setWorkModeMenuOpen] = useState(false);
const canToggleWorkMode = typeof onWorkModeChange === "function";
const attachInputRef = useRef<HTMLInputElement | null>(null);
const editorRef = useRef<MarkdownEditorRef>(null);
@@ -3221,32 +3222,53 @@ const IssueChatComposer = forwardRef<IssueChatComposerHandle, IssueChatComposerP
</>
) : null}
{canToggleWorkMode ? (
<Popover open={workModeMenuOpen} onOpenChange={setWorkModeMenuOpen}>
<PopoverTrigger asChild>
<Button
variant="ghost"
size="icon-sm"
data-testid="issue-chat-composer-work-mode-menu"
title="More composer options"
>
<MoreHorizontal className="h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-44 p-1" align="start">
<button
type="button"
data-testid="issue-chat-composer-work-mode-menu-toggle"
data-pending-work-mode={pendingWorkMode}
className={cn(
"flex w-full items-center gap-2 rounded px-2 py-1.5 text-xs hover:bg-accent/50",
isPlanning ? "text-amber-700 dark:text-amber-300" : "text-foreground",
)}
onClick={() => {
setPendingWorkMode((prev) => (prev === "planning" ? "standard" : "planning"));
setWorkModeMenuOpen(false);
}}
>
{isPlanning ? (
<Hammer className="h-3.5 w-3.5 shrink-0 text-muted-foreground" aria-hidden />
) : (
<ClipboardList className="h-3.5 w-3.5 shrink-0 text-amber-600 dark:text-amber-300" aria-hidden />
)}
<span>{isPlanning ? "Switch to standard" : "Switch to planning"}</span>
</button>
</PopoverContent>
</Popover>
) : null}
{canToggleWorkMode && isPlanning ? (
<button
type="button"
data-testid="issue-chat-composer-work-mode-toggle"
data-pending-work-mode={pendingWorkMode}
aria-pressed={isPlanning}
title={
isPlanning
? "Planning mode is on for this submission. Click to switch to Standard."
: "Click to switch this submission to Planning mode."
}
onClick={() =>
setPendingWorkMode((prev) => (prev === "planning" ? "standard" : "planning"))
}
className={cn(
"inline-flex items-center gap-1.5 rounded-md border px-2 py-1 text-xs transition-colors",
isPlanning
? "border-amber-500/60 bg-amber-500/15 text-amber-800 hover:bg-amber-500/25 dark:border-amber-500/50 dark:bg-amber-500/15 dark:text-amber-200 dark:hover:bg-amber-500/25"
: "border-border text-muted-foreground hover:bg-accent/50",
)}
aria-pressed
title="Planning mode is on for this submission. Click to switch to Standard."
onClick={() => setPendingWorkMode("standard")}
className="inline-flex items-center gap-1.5 rounded-md border border-amber-500/60 bg-amber-500/15 px-2 py-1 text-xs text-amber-800 transition-colors hover:bg-amber-500/25 dark:border-amber-500/50 dark:bg-amber-500/15 dark:text-amber-200 dark:hover:bg-amber-500/25"
>
{isPlanning ? (
<ClipboardList className="h-3.5 w-3.5" aria-hidden />
) : (
<Hammer className="h-3.5 w-3.5" aria-hidden />
)}
<span>{isPlanning ? "Planning" : "Standard"}</span>
<ClipboardList className="h-3.5 w-3.5" aria-hidden />
<span>Planning</span>
</button>
) : null}
</div>