import { createContext, useContext } from "react"; import type { UseMutationResult } from "@tanstack/react-query"; import type { CompanySecret, RoutineDetail as RoutineDetailType, RoutineEnvConfig, RoutineVariable, } from "@paperclipai/shared"; import type { MarkdownEditorRef, MentionOption } from "../MarkdownEditor"; import type { InlineEntityOption } from "../InlineEntitySelector"; import type { RoutineHistoryDirtyFieldDescriptor } from "../RoutineHistoryTab"; import type { RoutineRunDialogSubmitData } from "../RoutineRunVariablesDialog"; import type { RoutineTriggerResponse, RotateRoutineTriggerResponse, } from "../../api/routines"; import type { agentsApi } from "../../api/agents"; import type { projectsApi } from "../../api/projects"; import type { secretsApi } from "../../api/secrets"; export const ROUTINE_SECTION_KEYS = [ "overview", "triggers", "variables", "secrets", "delivery", "runs", "activity", "history", ] as const; export type RoutineSectionKey = (typeof ROUTINE_SECTION_KEYS)[number]; /** Editable sections own a save bar; read-only (operate) sections do not. */ export const EDITABLE_SECTIONS: RoutineSectionKey[] = [ "overview", "triggers", "variables", "secrets", "delivery", ]; /** Which dirty-field keys belong to which section (for scoped save state). */ export const SECTION_FIELD_KEYS: Record = { overview: ["title", "description", "projectId", "assigneeAgentId", "priority"], variables: ["variables"], secrets: ["env"], delivery: ["concurrencyPolicy", "catchUpPolicy"], }; export type RoutineEditDraft = { title: string; description: string; projectId: string; assigneeAgentId: string; priority: string; concurrencyPolicy: string; catchUpPolicy: string; variables: RoutineVariable[]; env: RoutineEnvConfig | null; }; export type NewTriggerDraft = { kind: string; cronExpression: string; signingMode: string; replayWindowSec: string; }; export function createDefaultNewTrigger(): NewTriggerDraft { return { kind: "schedule", cronExpression: "0 10 * * *", signingMode: "bearer", replayWindowSec: "300", }; } export type SecretMessage = { title: string; entries: Array<{ webhookUrl: string; webhookSecret: string }>; }; type AgentList = Awaited>; type ProjectList = Awaited>; type SecretList = Awaited>; type RoutineRunList = Awaited>; type RoutineActivityList = Awaited< ReturnType >; export type RoutineDetailContextValue = { routine: RoutineDetailType; routineId: string; companyId: string; // edit state editDraft: RoutineEditDraft; setEditDraft: React.Dispatch>; routineDefaults: RoutineEditDraft; dirtyFields: RoutineHistoryDirtyFieldDescriptor[]; isEditDirty: boolean; sectionDirtyFields: (section: RoutineSectionKey) => RoutineHistoryDirtyFieldDescriptor[]; isSectionDirty: (section: RoutineSectionKey) => boolean; discardSection: (section: RoutineSectionKey) => void; // save saveRoutine: UseMutationResult; saveConflict: boolean; reloadLatest: () => void; // header / automation automationEnabled: boolean; automationLabel: string; automationLabelClassName: string; automationToggleDisabled: boolean; onToggleAutomation: () => void; onOpenRunDialog: () => void; runRoutinePending: boolean; // triggers newTrigger: NewTriggerDraft; setNewTrigger: React.Dispatch>; createTrigger: UseMutationResult; updateTrigger: UseMutationResult }, unknown>; deleteTrigger: UseMutationResult; rotateTrigger: UseMutationResult; // secrets secretMessage: SecretMessage | null; setSecretMessage: (message: SecretMessage | null) => void; copySecretValue: (label: string, value: string) => void; availableSecrets: SecretList; createSecret: UseMutationResult; // entities agents: AgentList; projects: ProjectList; agentById: Map; projectById: Map; assigneeOptions: InlineEntityOption[]; projectOptions: InlineEntityOption[]; recentAssigneeIds: string[]; recentProjectIds: string[]; mentionOptions: MentionOption[]; currentAssignee: AgentList[number] | null; currentProject: ProjectList[number] | null; // operate data routineRuns: RoutineRunList | undefined; activity: RoutineActivityList | undefined; hasLiveRun: boolean; activeIssueId: string | undefined; // refs titleInputRef: React.RefObject; descriptionEditorRef: React.RefObject; assigneeSelectorRef: React.RefObject; projectSelectorRef: React.RefObject; // history restore plumbing onHistoryRestoreSecretMaterials: ( response: import("../../api/routines").RestoreRoutineRevisionResponse, ) => void; onHistoryRestored: ( response: import("../../api/routines").RestoreRoutineRevisionResponse, ) => void; navigateToSection: (section: RoutineSectionKey, options?: { replace?: boolean }) => void; }; export const RoutineDetailContext = createContext(null); export function useRoutineDetail(): RoutineDetailContextValue { const value = useContext(RoutineDetailContext); if (!value) { throw new Error("useRoutineDetail must be used within a RoutineDetailContext provider"); } return value; }