useVersionDiff.ts2.3 KBView on GitHub
/**
 * Loads the selected session's markdown plus the previous session's markdown
 * in parallel, then runs `diffWords` to produce segments the
 * `HistoryDiffView` renders as inline coloured spans.
 *
 * Contract:
 *   - both queries are gated on the inputs being non-null.
 *   - when `prevToSeq` is null (i.e. the user selected the very first
 *     session), we return a single unchanged segment containing the
 *     selected markdown — there's nothing to diff against.
 */

import { useQueries } from '@tanstack/react-query';
import { diffWords } from 'diff';
import { useMemo } from 'react';
import { useTRPC } from '@/providers/query-provider';

export interface DiffSegment {
  value: string;
  added?: boolean;
  removed?: boolean;
}

export interface UseVersionDiffResult {
  segments: DiffSegment[] | null;
  selectedMarkdown: string | null;
  previousMarkdown: string | null;
  isLoading: boolean;
  error: unknown;
}

export function useVersionDiff(
  documentId: string | null | undefined,
  selectedToSeq: number | null,
  prevToSeq: number | null,
): UseVersionDiffResult {
  const trpc = useTRPC();

  const [selectedQ, prevQ] = useQueries({
    queries: [
      {
        ...trpc.documents.versionContent.queryOptions({
          documentId: documentId ?? '',
          seq: selectedToSeq ?? 0,
        }),
        enabled: !!documentId && selectedToSeq !== null,
      },
      {
        ...trpc.documents.versionContent.queryOptions({
          documentId: documentId ?? '',
          seq: prevToSeq ?? 0,
        }),
        enabled: !!documentId && prevToSeq !== null,
      },
    ],
  });

  const selectedMarkdown = selectedQ.data?.markdown ?? null;
  const previousMarkdown = prevQ.data?.markdown ?? null;

  const segments = useMemo<DiffSegment[] | null>(() => {
    if (selectedMarkdown == null) return null;
    if (prevToSeq === null) {
      return [{ value: selectedMarkdown }];
    }
    if (previousMarkdown == null) return null;
    return diffWords(previousMarkdown, selectedMarkdown) as DiffSegment[];
  }, [selectedMarkdown, previousMarkdown, prevToSeq]);

  const isLoading =
    selectedQ.isLoading || (prevToSeq !== null && prevQ.isLoading);

  return {
    segments,
    selectedMarkdown,
    previousMarkdown,
    isLoading,
    error: selectedQ.error ?? prevQ.error ?? null,
  };
}