mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-06 22:09:44 +02:00
The batch action toolbar hardcoded status="todo", priority="none", and a null assignee, so the status/priority/assignee pickers always checked a fixed row regardless of the selected issues. The batch write itself worked, but the picker mis-reported the current value, surfacing as "status always defaults to todo" (MUL-3510). The same defect applied to priority and assignee, across all five toolbar mount points. Derive the shared status/priority/assignee of the selected issues via a new commonIssueFields helper and feed it to the pickers; when the selection is mixed, pass an empty value so no row is checked. Pickers now accept a nullable current value, and AssigneePicker gains a `mixed` flag to distinguish an all-unassigned selection (check "No assignee") from a mixed one (check nothing). Each call site passes its issue universe, mirroring the skill list's selected-rows approach. Adds unit tests for commonIssueFields and a toolbar picker-wiring test. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import type {
|
|
Issue,
|
|
IssueStatus,
|
|
IssuePriority,
|
|
IssueAssigneeType,
|
|
} from "../types";
|
|
|
|
/**
|
|
* Shared assignee across a selection. `{ type: null, id: null }` means every
|
|
* selected issue is unassigned — a real shared value, distinct from a mixed
|
|
* selection (which {@link commonIssueFields} reports as `assignee: null`).
|
|
*/
|
|
export interface CommonAssignee {
|
|
type: IssueAssigneeType | null;
|
|
id: string | null;
|
|
}
|
|
|
|
/**
|
|
* The status / priority / assignee shared by every issue in a batch selection.
|
|
* A field is `null` when the selection is empty or the issues disagree
|
|
* ("mixed"). Batch property pickers use this to reflect the real common value
|
|
* and fall back to an empty (no-checkmark) state when the values differ,
|
|
* instead of asserting a hardcoded default.
|
|
*/
|
|
export interface CommonIssueFields {
|
|
status: IssueStatus | null;
|
|
priority: IssuePriority | null;
|
|
assignee: CommonAssignee | null;
|
|
}
|
|
|
|
/**
|
|
* Returns the value shared by every item, or `null` when the list is empty or
|
|
* the items disagree. Comparison is by primitive equality, so callers pass a
|
|
* scalar key (collapse composite values to a string before calling).
|
|
*/
|
|
function sharedValue<T>(values: readonly T[]): T | null {
|
|
if (values.length === 0) return null;
|
|
const first = values[0]!;
|
|
return values.every((v) => v === first) ? first : null;
|
|
}
|
|
|
|
const ASSIGNEE_KEY_SEP = "\u0000";
|
|
|
|
/**
|
|
* Collapse a polymorphic assignee (type + id, either nullable) into a single
|
|
* comparable key so all-unassigned issues compare equal to each other and
|
|
* distinct from any assigned actor.
|
|
*/
|
|
function assigneeKey(type: IssueAssigneeType | null, id: string | null): string {
|
|
return `${type ?? ""}${ASSIGNEE_KEY_SEP}${id ?? ""}`;
|
|
}
|
|
|
|
/**
|
|
* Derive the common status / priority / assignee of the selected issues.
|
|
* Pass the already-filtered selection (the issues that are actually selected),
|
|
* mirroring how the skill list filters its rows by `selectedIds` before
|
|
* handing them to its batch toolbar.
|
|
*/
|
|
export function commonIssueFields(issues: readonly Issue[]): CommonIssueFields {
|
|
const status = sharedValue(issues.map((i) => i.status));
|
|
const priority = sharedValue(issues.map((i) => i.priority));
|
|
|
|
const sharedAssigneeKey = sharedValue(
|
|
issues.map((i) => assigneeKey(i.assignee_type, i.assignee_id)),
|
|
);
|
|
const assignee =
|
|
sharedAssigneeKey !== null && issues.length > 0
|
|
? { type: issues[0]!.assignee_type, id: issues[0]!.assignee_id }
|
|
: null;
|
|
|
|
return { status, priority, assignee };
|
|
}
|