Files
kandev/apps/web/components/task-create-dialog-prop-builders.ts
2026-09-19 21:26:36 +01:00

196 lines
8.6 KiB
TypeScript

/**
* Pure prop-assembly helpers for the task-create dialog. Extracted from
* task-create-dialog.tsx so the orchestrator stays under the per-file line
* cap — these are non-React, no-JSX projections from the setup hook's
* result + dialog props, so they belong outside the component module.
*/
// Both names are used only in type positions (interface + `typeof` in
// ReturnType<>), so the `import type` form makes the otherwise-circular
// dependency with task-create-dialog.tsx explicitly type-only — bundlers
// and analysis tools won't treat it as a real runtime cycle.
import type { TaskCreateDialogProps } from "@/components/task-create-dialog";
import type { useTaskCreateDialogSetup } from "@/components/task-create-dialog-setup";
import type { DialogFormBodyProps, DialogFormState } from "@/components/task-create-dialog-types";
import {
resolveTaskCreateLaunchPreview,
type TaskCreateLaunchPreview,
} from "@/components/task-create-dialog-launch-preview";
import {
computeRunnerEditable,
computeRunnerIneligibleReason,
} from "@/components/task-create-dialog-helpers";
export function computeHasAllBranches(fs: DialogFormState): boolean {
if (fs.noRepository) return true;
if (fs.useRemote) {
const rows = fs.remoteRepos.filter((r) => r.url.trim() !== "");
return rows.length > 0 && rows.every((r) => !!r.branch);
}
return (
fs.repositories.length > 0 && fs.repositories.every((r) => Boolean(r.baseBranch || r.branch))
);
}
export function localRepositoryCreationEnabled(isCreateMode: boolean, repoLocked: boolean) {
return isCreateMode && !repoLocked;
}
export function resolveDialogLaunchPreview(
isCreateMode: boolean,
effectiveWorkflowId: string | null,
fetchedSteps: DialogFormState["fetchedSteps"],
snapshots: DialogFormBodyProps["snapshots"],
hasDescription: boolean,
): TaskCreateLaunchPreview | null {
if (!isCreateMode) return null;
return resolveTaskCreateLaunchPreview({
effectiveWorkflowId,
fetchedSteps,
snapshotSteps: effectiveWorkflowId ? snapshots[effectiveWorkflowId]?.steps : undefined,
launchIntent: hasDescription ? "start-agent" : "plan-mode",
});
}
export function buildDialogFormBodyProps(
setup: ReturnType<typeof useTaskCreateDialogSetup>,
props: TaskCreateDialogProps,
): DialogFormBodyProps {
const { fs, computed, handlers } = setup;
const repoLocked = !!props.lockedFields?.repository;
const effectiveWorkflowId = computed.effectiveWorkflowId ?? null;
const workflowAgentOverrideState = setup.workflowAgentOverrideValidation;
return {
isSessionMode: setup.isSessionMode,
isCreateMode: setup.isCreateMode,
isEditMode: setup.isEditMode,
autoTitle: setup.autoTitle,
isTaskStarted: setup.isTaskStarted,
onTaskNameChange: handlers.handleTaskNameChange,
onRowRepositoryChange: handlers.handleRowRepositoryChange,
onRowBranchChange: handlers.handleRowBranchChange,
onRowPolicyChange: handlers.handleRowPolicyChange,
initialDescription: fs.currentDefaults.description,
workspaceId: props.workspaceId,
onJiraImport: setup.handleJiraImport,
onLinearImport: setup.handleLinearImport,
agentProfileOptions: computed.agentProfileOptions,
executorProfileOptions: computed.executorProfileOptions,
agentProfiles: setup.agentProfiles,
agentProfilesLoading: computed.agentProfilesLoading,
executorsLoading: computed.executorsLoading,
isCreatingSession: fs.isCreatingSession,
isCreatingTask: fs.isCreatingTask,
workflows: setup.workflows,
snapshots: setup.snapshots,
effectiveWorkflowId,
launchPreview: resolveDialogLaunchPreview(
setup.isCreateMode,
effectiveWorkflowId,
fs.fetchedSteps,
setup.snapshots,
fs.hasDescription,
),
fs,
editDependencies: setup.editDependencies,
handleKeyDown: setup.handleKeyDown,
onAgentProfileChange: handlers.handleAgentProfileChange,
onExecutorProfileChange: handlers.handleExecutorProfileChange,
onWorkflowChange: handlers.handleWorkflowChange,
onToggleRemote: repoLocked ? undefined : handlers.handleToggleRemote,
onToggleFreshBranch: handlers.handleToggleFreshBranch,
onToggleNoRepository: repoLocked ? undefined : handlers.handleToggleNoRepository,
onWorkspacePathChange: handlers.handleWorkspacePathChange,
localRepositoryCreation: localRepositoryCreationEnabled(setup.isCreateMode, repoLocked)
? {
executorSelection: handlers.directLocalExecutorSelection,
onCreated: handlers.handleLocalRepositoryCreated,
}
: undefined,
enhance: setup.enhance,
workflowAgentLocked: computed.workflowAgentLocked,
repositories: setup.repositories,
onRefreshRepositories: setup.refreshRepositories,
repositoriesRefreshing: setup.repositoriesLoading,
lastUsedBranch: setup.taskCreateLastUsed.branch,
userSettingsLoaded: setup.userSettingsLoaded,
freshBranchAvailable: setup.freshBranchAvailable,
// The same lock disables the source-mode and local-repository controls, and
// applying a set writes fs.repositories just as they would.
repositorySets: repoLocked ? undefined : setup.repositorySets,
isLocalExecutor: computed.isLocalExecutor,
agentCompatState: computed.agentCompatState,
selectedAgentProfileName: computed.selectedAgentProfileName,
effectiveWorkflowName: resolveWorkflowName(setup.workflows, computed.effectiveWorkflowId),
executorProfileName: computed.selectedExecutorProfileName,
extraFormSlot: props.extraFormSlot,
aboveDescriptionSlot: props.aboveDescriptionSlot,
bottomSlot: props.bottomSlot,
descriptionPlaceholder: props.descriptionPlaceholder,
workflowLocked: props.lockedFields?.workflow,
workflowAgentOverrideRows: workflowAgentOverrideState.rows,
workflowAgentOverrideOptions: workflowAgentOverrideState.options,
workflowAgentOverridesLoading: workflowAgentOverrideState.loading,
workflowAgentOverridesInvalid: workflowAgentOverrideState.invalid,
workflowAgentOverridesError: workflowAgentOverrideState.error,
onWorkflowAgentOverrideChange: (sourceProfileId, replacementProfileId) => {
const next = { ...fs.workflowAgentOverrides };
if (replacementProfileId) next[sourceProfileId] = replacementProfileId;
else delete next[sourceProfileId];
fs.setWorkflowAgentOverrides(next);
},
onResetWorkflowAgentOverrides: () => fs.setWorkflowAgentOverrides({}),
onRetryWorkflowAgentOverrides: () => setup.refreshWorkspaceSnapshots(true),
runnerEditable: computeRunnerEditable(setup.isEditMode, props.editingTask),
runnerIneligibleReason: computeRunnerIneligibleReason(props.editingTask),
};
}
/** Name of the effective workflow, for copy that has to name it. */
export function resolveWorkflowName(
workflows: ReadonlyArray<{ id: string; name: string }>,
effectiveWorkflowId: string | null | undefined,
): string | null {
if (!effectiveWorkflowId) return null;
return workflows.find((workflow) => workflow.id === effectiveWorkflowId)?.name ?? null;
}
export function buildDialogFooterProps(
setup: ReturnType<typeof useTaskCreateDialogSetup>,
props: TaskCreateDialogProps,
pendingAttachmentUploadReason?: string | null,
) {
const { fs, computed, submitHandlers } = setup;
const workflowAgentOverridesBlockedReason = setup.workflowAgentOverrideValidation.blockedReason;
return {
isSessionMode: setup.isSessionMode,
isCreateMode: setup.isCreateMode,
isEditMode: setup.isEditMode,
autoTitle: setup.autoTitle,
isTaskStarted: setup.isTaskStarted,
isCreatingSession: fs.isCreatingSession,
isCreatingTask: fs.isCreatingTask,
hasTitle: fs.hasTitle,
hasDescription: fs.hasDescription,
hasRepositorySelection: computed.hasRepositorySelection,
hasAllBranches: computeHasAllBranches(fs),
agentProfileId: computed.effectiveAgentProfileId,
workspaceId: props.workspaceId,
effectiveWorkflowId: computed.effectiveWorkflowId ?? null,
executorHint: computed.executorHint,
noCompatibleAgent: computed.noCompatibleAgent,
agentCompatState: computed.agentCompatState,
selectedAgentProfileName: computed.selectedAgentProfileName,
executorProfileName: computed.selectedExecutorProfileName,
onCancel: submitHandlers.handleCancel,
onUpdateWithoutAgent: submitHandlers.handleUpdateWithoutAgent,
onCreateWithoutAgent: submitHandlers.handleCreateWithoutAgent,
onCreateWithPlanMode: submitHandlers.handleCreateWithPlanMode,
submitBlockedReason:
props.submitBlockedReason ??
pendingAttachmentUploadReason ??
setup.savedBaseSubmitBlockedReason ??
workflowAgentOverridesBlockedReason,
editDependenciesReady: setup.isEditMode ? setup.editDependencies.ready : undefined,
};
}