enter-navigation.ts
1 export type AnycopyEnterNavigationResult = "reopen" | "closed"; 2 3 export type AnycopySummaryChoice = "No summary" | "Summarize" | "Summarize with custom prompt"; 4 5 export type AnycopyEnterNavigationDeps = { 6 entryId: string; 7 currentLeafIdForNoop: string | null; 8 skipSummaryPrompt: boolean; 9 close: () => void; 10 reopen: (options: { initialSelectedId: string }) => void; 11 navigateTree: ( 12 targetId: string, 13 options?: { summarize?: boolean; customInstructions?: string }, 14 ) => Promise<{ cancelled: boolean; aborted?: boolean }>; 15 ui: { 16 select: (title: string, options: AnycopySummaryChoice[]) => Promise<AnycopySummaryChoice | undefined>; 17 editor: (title: string) => Promise<string | undefined>; 18 setStatus: (source: string, message: string) => void; 19 setWorkingMessage: (message?: string) => void; 20 notify: (message: string, level: "error") => void; 21 }; 22 }; 23 24 export function createAnycopyEnterNavigationLauncher( 25 run: (entryId: string) => Promise<AnycopyEnterNavigationResult>, 26 ): (entryId: string) => void { 27 let navigationInFlight = false; 28 29 return (entryId: string) => { 30 if (navigationInFlight) { 31 return; 32 } 33 34 navigationInFlight = true; 35 void run(entryId).finally(() => { 36 navigationInFlight = false; 37 }); 38 }; 39 } 40 41 export async function runAnycopyEnterNavigation( 42 deps: AnycopyEnterNavigationDeps, 43 ): Promise<AnycopyEnterNavigationResult> { 44 const { entryId, currentLeafIdForNoop, skipSummaryPrompt, close, reopen, navigateTree, ui } = deps; 45 46 if (currentLeafIdForNoop !== null && entryId === currentLeafIdForNoop) { 47 close(); 48 ui.setStatus("anycopy", "Already at this point"); 49 return "closed"; 50 } 51 52 close(); 53 54 let wantsSummary = false; 55 let customInstructions: string | undefined; 56 57 if (!skipSummaryPrompt) { 58 while (true) { 59 const choice = await ui.select("Summarize branch?", [ 60 "No summary", 61 "Summarize", 62 "Summarize with custom prompt", 63 ]); 64 65 if (choice === undefined) { 66 reopen({ initialSelectedId: entryId }); 67 return "reopen"; 68 } 69 70 if (choice === "No summary") { 71 wantsSummary = false; 72 customInstructions = undefined; 73 break; 74 } 75 76 if (choice === "Summarize") { 77 wantsSummary = true; 78 customInstructions = undefined; 79 break; 80 } 81 82 customInstructions = await ui.editor("Custom summarization instructions"); 83 if (customInstructions === undefined) { 84 continue; 85 } 86 87 wantsSummary = true; 88 break; 89 } 90 } 91 92 ui.setWorkingMessage("Navigating treeā¦"); 93 94 try { 95 const result = await navigateTree(entryId, { 96 summarize: wantsSummary, 97 customInstructions, 98 }); 99 100 if (result.cancelled) { 101 if (wantsSummary) { 102 ui.setStatus("anycopy", "Branch summarization cancelled"); 103 reopen({ initialSelectedId: entryId }); 104 return "reopen"; 105 } 106 107 ui.setStatus("anycopy", "Navigation cancelled"); 108 return "closed"; 109 } 110 111 return "closed"; 112 } catch (error) { 113 ui.notify(error instanceof Error ? error.message : String(error), "error"); 114 return "closed"; 115 } finally { 116 ui.setWorkingMessage(); 117 } 118 }