fix(panel): stop the task status dropdown duplicating the current status

The status Select always renders the current status as its first item, then
appends the valid-transitions list. When a task changed state (e.g. on
approve-and-merge to completed) the cached valid-transitions query was not
refetched, so it still held the previous state's targets — which include the
now-current status. That yields two SelectItems with the same value; Radix
requires unique values, so the list showed a duplicate entry and the trigger
label rendered doubled ("Completed Completed").

Key the valid-transitions query on the task status so it refetches on every
state change, and filter the current status out of the appended list so it can
never duplicate the always-rendered current item.
This commit is contained in:
Renn F
2026-06-14 13:56:23 +02:00
parent 6cf99a1b0a
commit ddd9c7a38f
2 changed files with 16 additions and 6 deletions
@@ -97,8 +97,14 @@ export function TaskHeader({ task, onAction }: TaskHeaderProps) {
const updateTask = useUpdateTask();
// Fetch valid next statuses from GET /tasks/{id}/valid-transitions.
// Falls back to [] while loading or on error — the Select is disabled during loading.
const { data: validTransitionsData, isLoading: isTransitionsLoading } = useTaskValidTransitions(task.id);
const nextStatuses: TaskStatus[] = validTransitionsData ?? [];
const { data: validTransitionsData, isLoading: isTransitionsLoading } = useTaskValidTransitions(task.id, task.status);
// Exclude the current status: it is always rendered first (below), so a stale
// cache or a backend list that re-includes it would duplicate the item. Radix
// Select requires unique item values, so a duplicate also garbles the trigger
// label (it renders as e.g. "Completed Completed").
const nextStatuses: TaskStatus[] = (validTransitionsData ?? []).filter(
(s) => s !== task.status
);
const [deleteOpen, setDeleteOpen] = useState(false);
// Inline editing states
+8 -4
View File
@@ -76,15 +76,19 @@ export function useSubtasks(parentTaskId: string) {
/**
* Fetches valid next statuses for a task from GET /tasks/{id}/valid-transitions.
* Returns undefined while loading; on error (including 404) gracefully returns
* undefined so callers can fall back to a hardcoded map.
* undefined and the caller renders just the current status.
*
* `status` is part of the query key so the transitions refetch when the task
* moves to a new state — otherwise a stale list (e.g. the pre-merge transitions)
* lingers and can re-list the now-current status.
*/
export function useTaskValidTransitions(taskId: string) {
export function useTaskValidTransitions(taskId: string, status?: string) {
return useQuery<TaskStatus[]>({
queryKey: ["tasks", "valid-transitions", taskId] as const,
queryKey: ["tasks", "valid-transitions", taskId, status] as const,
queryFn: () => tasksApi.getValidTransitions(taskId),
enabled: !!taskId,
staleTime: 30000, // 30 seconds
retry: false, // don't retry on 404 or other errors — caller falls back to hardcoded map
retry: false, // don't retry on 404/errors — caller renders just the current status
});
}