diff --git a/ui/src/components/IssueFiltersPopover.test.tsx b/ui/src/components/IssueFiltersPopover.test.tsx index 4fe6a11c12..718041bd66 100644 --- a/ui/src/components/IssueFiltersPopover.test.tsx +++ b/ui/src/components/IssueFiltersPopover.test.tsx @@ -81,4 +81,29 @@ describe("IssueFiltersPopover", () => { expect(layoutGrid?.className).toContain("grid-cols-1"); expect(popoverContent?.textContent).toContain("Live runs only"); }); + + it("hides the Priority filter section while priority UI is off (PAP-411)", () => { + const root = createRoot(container); + + act(() => { + root.render( + , + ); + }); + + const popoverContent = container.querySelector("[data-testid='popover-content']"); + expect(popoverContent).not.toBeNull(); + // Status section still renders, Priority section is gated off (PAP-411). + expect(popoverContent?.textContent).toContain("Status"); + expect(popoverContent?.textContent).not.toContain("Priority"); + }); }); diff --git a/ui/src/components/IssueFiltersPopover.tsx b/ui/src/components/IssueFiltersPopover.tsx index d702bad7b5..d491c74c1e 100644 --- a/ui/src/components/IssueFiltersPopover.tsx +++ b/ui/src/components/IssueFiltersPopover.tsx @@ -6,6 +6,7 @@ import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Bot, Filter, HardDrive, Search, User, X } from "lucide-react"; import { PriorityIcon } from "./PriorityIcon"; +import { SHOW_TASK_PRIORITY_UI } from "../lib/ui-flags"; import { StatusIcon } from "./StatusIcon"; import { defaultIssueFilterState, @@ -191,6 +192,8 @@ export function IssueFiltersPopover({ + {/* PAP-411: Priority filter section hidden behind SHOW_TASK_PRIORITY_UI (filter state stays intact). */} + {SHOW_TASK_PRIORITY_UI && (
Priority
@@ -206,6 +209,7 @@ export function IssueFiltersPopover({ ))}
+ )}
diff --git a/ui/src/components/IssueProperties.test.tsx b/ui/src/components/IssueProperties.test.tsx index 5d795de7ce..40d8a2be4d 100644 --- a/ui/src/components/IssueProperties.test.tsx +++ b/ui/src/components/IssueProperties.test.tsx @@ -515,6 +515,25 @@ describe("IssueProperties", () => { act(() => root.unmount()); }); + it("hides the Priority property row while priority UI is off (PAP-411)", async () => { + const root = renderProperties(container, { + issue: createIssue({ priority: "high" }), + childIssues: [], + onUpdate: vi.fn(), + inline: true, + }); + await flush(); + + await waitForAssertion(() => { + // The Triage section still renders the Status row... + expect(container.querySelector('[data-property-label="Status"]')).not.toBeNull(); + // ...but the Priority row is gated behind SHOW_TASK_PRIORITY_UI (off). + expect(container.querySelector('[data-property-label="Priority"]')).toBeNull(); + }); + + act(() => root.unmount()); + }); + it("shows assignee and originating without responsible wording", async () => { mockAgentsApi.list.mockResolvedValue([{ id: "agent-1", name: "CodexCoder", status: "active", adapterType: "codex_local" }]); const root = renderProperties(container, { diff --git a/ui/src/components/IssueThreadInteractionCard.tsx b/ui/src/components/IssueThreadInteractionCard.tsx index 633f2f650b..d705484268 100644 --- a/ui/src/components/IssueThreadInteractionCard.tsx +++ b/ui/src/components/IssueThreadInteractionCard.tsx @@ -32,6 +32,7 @@ import { Button } from "./ui/button"; import { Checkbox } from "./ui/checkbox"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible"; import { PriorityIcon } from "./PriorityIcon"; +import { SHOW_TASK_PRIORITY_UI } from "../lib/ui-flags"; import { Textarea } from "./ui/textarea"; import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { Badge } from "@/components/ui/badge"; @@ -512,7 +513,8 @@ function TaskTreeNode({ ) : null}
- {node.task.priority ? ( + {/* PAP-411: priority UI hidden behind SHOW_TASK_PRIORITY_UI. */} + {SHOW_TASK_PRIORITY_UI && node.task.priority ? ( { }); }); + it("hides the Priority option from the Sort and Group menus while priority UI is off (PAP-411)", async () => { + const { root } = renderWithQueryClient( + undefined} + />, + container, + ); + + await waitForAssertion(() => { + expect(container.querySelectorAll('[data-testid="issue-row"]').length).toBeGreaterThan(0); + }); + + const sortButton = Array.from(container.querySelectorAll("button")).find( + (button) => button.getAttribute("title") === "Sort", + ); + expect(sortButton).toBeTruthy(); + act(() => { + sortButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await waitForAssertion(() => { + const labels = Array.from(document.body.querySelectorAll("button")).map((b) => b.textContent ?? ""); + // Status sort option renders, but the Priority option is gated off (PAP-411). + expect(labels.some((text) => text.includes("Status"))).toBe(true); + expect(labels.some((text) => text.includes("Priority"))).toBe(false); + }); + + const groupButton = Array.from(container.querySelectorAll("button")).find( + (button) => button.getAttribute("title") === "Group", + ); + expect(groupButton).toBeTruthy(); + act(() => { + groupButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await waitForAssertion(() => { + const labels = Array.from(document.body.querySelectorAll("button")).map((b) => b.textContent ?? ""); + expect(labels.some((text) => text.includes("Status"))).toBe(true); + expect(labels.some((text) => text.includes("Priority"))).toBe(false); + }); + + act(() => { + root.unmount(); + }); + }); + it("hides the workflow blocker chip when a sub-issue is blocked only by its previous sibling", async () => { const firstChild = createIssue({ id: "issue-first-child", diff --git a/ui/src/components/IssuesList.tsx b/ui/src/components/IssuesList.tsx index 2aeb017322..d463691c55 100644 --- a/ui/src/components/IssuesList.tsx +++ b/ui/src/components/IssuesList.tsx @@ -46,6 +46,7 @@ import { type InboxIssueColumn, } from "../lib/inbox"; import { cn, formatDurationMs, formatTokens } from "../lib/utils"; +import { SHOW_TASK_PRIORITY_UI } from "../lib/ui-flags"; import { collectSubtreeLiveCounts } from "../lib/liveIssueIds"; import { InboxIssueMetaLeading, @@ -1827,6 +1828,7 @@ export function IssuesList({
+ {/* PAP-411: "priority" sort option hidden behind SHOW_TASK_PRIORITY_UI (comparator stays dormant). */} {([ ["workflow", "Workflow"], ["status", "Status"], @@ -1834,7 +1836,9 @@ export function IssuesList({ ["title", "Title"], ["created", "Created"], ["updated", "Updated"], - ] as const).map(([field, label]) => ( + ] as const) + .filter(([field]) => SHOW_TASK_PRIORITY_UI || field !== "priority") + .map(([field, label]) => ( + {/* PAP-411: mobile priority section hidden behind SHOW_TASK_PRIORITY_UI. */} + {SHOW_TASK_PRIORITY_UI && (
Priority @@ -2211,6 +2216,7 @@ export function NewIssueDialog() { ))}
+ )}
+ {/* PAP-411: PriorityIcon showcase gated behind SHOW_TASK_PRIORITY_UI per board decision. */} + {SHOW_TASK_PRIORITY_UI && (
{["critical", "high", "medium", "low"].map((p) => ( @@ -651,6 +657,7 @@ export function DesignGuide() { Click the icon to change (current: {priority})
+ )}
@@ -1123,7 +1130,8 @@ export function DesignGuide() { leading={ <> - + {/* PAP-411: PriorityIcon hidden behind SHOW_TASK_PRIORITY_UI. */} + {SHOW_TASK_PRIORITY_UI && } } identifier="PAP-001" @@ -1136,7 +1144,7 @@ export function DesignGuide() { leading={ <> - + {SHOW_TASK_PRIORITY_UI && } } identifier="PAP-002" @@ -1149,7 +1157,7 @@ export function DesignGuide() { leading={ <> - + {SHOW_TASK_PRIORITY_UI && } } identifier="PAP-003" @@ -1161,7 +1169,7 @@ export function DesignGuide() { leading={ <> - + {SHOW_TASK_PRIORITY_UI && } } identifier="PAP-004" @@ -1249,7 +1257,10 @@ export function DesignGuide() { onClick={() => setFilters([ { key: "status", label: "Status", value: "Active" }, - { key: "priority", label: "Priority", value: "High" }, + // PAP-411: priority filter demo row suppressed while SHOW_TASK_PRIORITY_UI is off. + ...(SHOW_TASK_PRIORITY_UI + ? [{ key: "priority", label: "Priority", value: "High" } as FilterValue] + : []), ]) } > @@ -1429,10 +1440,13 @@ export function DesignGuide() { Status
-
- Priority - -
+ {/* PAP-411: priority metadata row hidden behind SHOW_TASK_PRIORITY_UI. */} + {SHOW_TASK_PRIORITY_UI && ( +
+ Priority + +
+ )}
Responsible
@@ -1500,14 +1514,15 @@ export function DesignGuide() { 2
+ {/* PAP-411: leading PriorityIcon hidden behind SHOW_TASK_PRIORITY_UI. */} } + leading={SHOW_TASK_PRIORITY_UI ? : undefined} identifier="PAP-101" title="Build agent heartbeat system" onClick={() => {}} /> } + leading={SHOW_TASK_PRIORITY_UI ? : undefined} identifier="PAP-102" title="Add cost tracking dashboard" onClick={() => {}} diff --git a/ui/src/pages/IssueDetail.test.tsx b/ui/src/pages/IssueDetail.test.tsx index fa0d1b5c4a..8ef40448e7 100644 --- a/ui/src/pages/IssueDetail.test.tsx +++ b/ui/src/pages/IssueDetail.test.tsx @@ -1108,7 +1108,7 @@ describe("IssueDetail", () => { expect(mockDecisionsApi.list).not.toHaveBeenCalled(); }); - it("updates status and priority from the task header controls", async () => { + it("updates status from the task header control and hides the priority control (PAP-411)", async () => { const issue = createIssue({ status: "todo", priority: "medium" }); mockIssuesApi.get.mockResolvedValue(issue); mockIssuesApi.update.mockImplementation(async (_issueId: string, data: Record) => ({ @@ -1133,7 +1133,9 @@ describe("IssueDetail", () => { 'button[aria-label="Change priority (current: medium)"]', ); expect(statusButton).not.toBeNull(); - expect(priorityButton).not.toBeNull(); + // PAP-411: priority UI is hidden behind SHOW_TASK_PRIORITY_UI (off), so the header + // priority control must not render. + expect(priorityButton).toBeNull(); await act(async () => { statusButton!.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true })); @@ -1141,13 +1143,10 @@ describe("IssueDetail", () => { await waitForAssertion(() => { expect(mockIssuesApi.update).toHaveBeenCalledWith(issue.identifier, { status: "done" }); }); - - await act(async () => { - priorityButton!.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true })); - }); - await waitForAssertion(() => { - expect(mockIssuesApi.update).toHaveBeenCalledWith(issue.identifier, { priority: "high" }); - }); + expect(mockIssuesApi.update).not.toHaveBeenCalledWith( + issue.identifier, + expect.objectContaining({ priority: expect.anything() }), + ); mockIssuesApi.update.mockReset(); }); diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index 483fb2860d..1c632986e4 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -135,6 +135,7 @@ import { ArtifactFileChip } from "../components/ArtifactFileChip"; import { ScrollToBottom } from "../components/ScrollToBottom"; import { StatusIcon } from "../components/StatusIcon"; import { PriorityIcon } from "../components/PriorityIcon"; +import { SHOW_TASK_PRIORITY_UI } from "../lib/ui-flags"; import { ProductivityReviewBadge } from "../components/ProductivityReviewBadge"; import { Identity } from "../components/Identity"; import { PluginSlotMount, PluginSlotOutlet, usePluginSlots } from "@/plugins/slots"; @@ -706,7 +707,8 @@ function IssueDetailLoadingState({ {headerSeed ? ( <> - + {/* PAP-411: priority UI hidden behind SHOW_TASK_PRIORITY_UI. */} + {SHOW_TASK_PRIORITY_UI && } {identifier ? ( {identifier} ) : null} @@ -4195,10 +4197,13 @@ export function IssueDetail() { blockerAttention={issue.blockerAttention} onChange={(status) => updateIssue.mutate({ status })} /> - updateIssue.mutate({ priority })} - /> + {/* PAP-411: priority UI hidden behind SHOW_TASK_PRIORITY_UI. */} + {SHOW_TASK_PRIORITY_UI && ( + updateIssue.mutate({ priority })} + /> + )} {issue.identifier ?? issue.id.slice(0, 8)} {hasLiveRuns && (