| 1 | import { compactArchivedToolItems } from "./archivedToolItems"; |
| 2 | import { duplicateLiveItemIds } from "./hydrateHistoryApply"; |
| 3 | import { historyRevisionIsOlder } from "./sessionTranscriptMode"; |
| 4 | import type { HistoryMutation, Item } from "./useController"; |
| 5 | import { canonicalUserConfirmations, settleLocalSubmissions, type LocalSubmissionFields } from "./localSubmissionState"; |
| 6 | |
| 7 | type WindowFields = LocalSubmissionFields & { |
| 8 | items: Item[]; |
| 9 | historyPrefixCount: number; |
| 10 | hydrateHistoryLoaded?: boolean; |
| 11 | hydratePlaceholderItems?: Item[]; |
| 12 | historyStartTurn: number; |
| 13 | historyEndTurn: number; |
| 14 | historyTotalTurns: number; |
| 15 | historyHasOlder: boolean; |
| 16 | historyHasNewer: boolean; |
| 17 | historyOlderLoading: boolean; |
| 18 | historyOlderError?: string; |
| 19 | historyNewerLoading: boolean; |
| 20 | historyNewerError?: string; |
| 21 | historyRevision?: number; |
| 22 | historyDigest?: string; |
| 23 | historyLayoutRevision: number; |
| 24 | historyMutation: HistoryMutation; |
| 25 | pendingSubmissionId?: string; |
| 26 | }; |
| 27 | |
| 28 | export type HistoryWindowMutationAction = { |
| 29 | type: "history_replace" | "history_rebase" | "history_prepend" | "history_append"; |
| 30 | items: Item[]; |
| 31 | removeIds?: string[]; |
| 32 | startTurn: number; |
| 33 | endTurn?: number; |
| 34 | totalTurns: number; |
| 35 | hasOlder: boolean; |
| 36 | hasNewer?: boolean; |
| 37 | revision?: number; |
| 38 | digest?: string; |
| 39 | }; |
| 40 | |
| 41 | function common<S extends WindowFields>(state: S, action: HistoryWindowMutationAction, items: Item[], prefix: number, kind: "replace" | "prepend" | "append"): S { |
| 42 | return { |
| 43 | ...state, |
| 44 | items: compactArchivedToolItems(items), |
| 45 | historyPrefixCount: prefix, |
| 46 | hydrateHistoryLoaded: true, |
| 47 | hydratePlaceholderItems: undefined, |
| 48 | historyStartTurn: action.startTurn, |
| 49 | historyEndTurn: action.endTurn ?? (kind === "prepend" ? state.historyEndTurn : action.totalTurns), |
| 50 | historyTotalTurns: action.totalTurns, |
| 51 | historyHasOlder: action.hasOlder, |
| 52 | historyHasNewer: Boolean(action.hasNewer), |
| 53 | historyOlderLoading: false, |
| 54 | historyOlderError: undefined, |
| 55 | historyNewerLoading: false, |
| 56 | historyNewerError: undefined, |
| 57 | historyRevision: action.revision, |
| 58 | historyDigest: action.digest, |
| 59 | historyMutation: { seq: state.historyMutation.seq + 1, kind }, |
| 60 | }; |
| 61 | } |
| 62 | |
| 63 | export function reduceHistoryWindowState<S extends WindowFields>(state: S, action: HistoryWindowMutationAction): S { |
| 64 | if (historyRevisionIsOlder(state.historyRevision, action.revision)) return state; |
| 65 | if (action.type === "history_replace") { |
| 66 | return settleLocalSubmissions(common(state, action, action.items, action.items.length, "replace"), action.items); |
| 67 | } |
| 68 | if (action.type === "history_rebase") { |
| 69 | const liveTail = state.items.slice(Math.min(state.historyPrefixCount, state.items.length)); |
| 70 | const duplicates = new Set(duplicateLiveItemIds(action.items, liveTail)); |
| 71 | const items = [...action.items, ...liveTail.filter((item) => !duplicates.has(item.id))]; |
| 72 | return settleLocalSubmissions({ ...common(state, action, items, action.items.length, "replace"), historyLayoutRevision: state.historyLayoutRevision + 1 }, items, canonicalUserConfirmations(action.items)); |
| 73 | } |
| 74 | if (action.type === "history_prepend") { |
| 75 | const remove = action.removeIds?.length ? new Set(action.removeIds) : undefined; |
| 76 | const rest = remove ? state.items.filter((item) => !remove.has(item.id)) : state.items; |
| 77 | const prefix = state.items.slice(0, Math.min(state.historyPrefixCount, state.items.length)); |
| 78 | const retainedPrefix = remove ? prefix.filter((item) => !remove.has(item.id)) : prefix; |
| 79 | const incoming = new Set(action.items.map(item => item.id)); |
| 80 | const items = [...new Map(action.items.map(item => [item.id, item])).values(), ...rest.filter(item => !incoming.has(item.id))]; |
| 81 | return settleLocalSubmissions(common(state, action, items, action.items.length + retainedPrefix.filter(item => !incoming.has(item.id)).length, "prepend"), items, canonicalUserConfirmations(action.items)); |
| 82 | } |
| 83 | return settleLocalSubmissions({ |
| 84 | ...common(state, action, action.items, action.items.length, "append"), |
| 85 | historyLayoutRevision: state.historyLayoutRevision + 1, |
| 86 | }, action.items); |
| 87 | } |
| 88 |