1/**
2 * Shared `@pierre/diffs` plumbing for the lazy-loaded code/diff views.
3 *
4 * IMPORTANT: this module (transitively) pulls in the whole diffs+shiki
5 * runtime. Only `React.lazy` chunks (`pages/Commit.tsx`,
6 * `pages/FileView.tsx`) may import it — never `main.tsx`/`Repo.tsx`
7 * statically — so the library stays out of the initial bundle.
8 *
9 * Reference equality is the library's re-render contract: every
10 * `options`/`file`/`fileDiff` object handed to a component must be
11 * memoized (the hooks below key their `useMemo` on the resolved theme).
12 * `themeType` is always pinned to our toggle — the components render
13 * into a shadow root that would otherwise follow the OS, not the app's
14 * `.dark` class.
15 */
16import type { FileDiffOptions, FileOptions } from "@pierre/diffs";
17import { useMemo } from "react";
18import { useTheme } from "./theme.tsx";
19
20/** Shiki theme pair matching the app palette (see `useDiffThemeOptions`). */
21export const DIFF_THEMES = {
22 light: "github-light-default",
23 dark: "github-dark-default",
24} as const;
25
26/** Memoized options for `<FileDiff>`: unified view, app-owned header. */
27export const useFileDiffOptions = (): FileDiffOptions<undefined, undefined> => {
28 const { resolved } = useTheme();
29 return useMemo(
30 () => ({
31 theme: DIFF_THEMES,
32 themeType: resolved,
33 diffStyle: "unified",
34 disableFileHeader: true,
35 }),
36 [resolved],
37 );
38};
39
40/** Memoized options for `<File>` (plain blob view, app-owned header). */
41export const useFileOptions = (): FileOptions<undefined, undefined> => {
42 const { resolved } = useTheme();
43 return useMemo(
44 () => ({
45 theme: DIFF_THEMES,
46 themeType: resolved,
47 disableFileHeader: true,
48 }),
49 [resolved],
50 );
51};
52
53/** Blobs bigger than this are never fetched/rendered as text diffs. */
54export const MAX_RENDER_BYTES = 1024 * 1024;
55
56/** Plain promise concurrency limiter (the SPA ships no Effect runtime). */
57export const makeLimiter = (max: number) => {
58 let active = 0;
59 const queue: Array<() => void> = [];
60 const release = () => {
61 active--;
62 queue.shift()?.();
63 };
64 return <T,>(task: () => Promise<T>): Promise<T> =>
65 new Promise<T>((resolve, reject) => {
66 const run = () => {
67 active++;
68 task().then(resolve, reject).finally(release);
69 };
70 if (active < max) run();
71 else queue.push(run);
72 });
73};
74
75/** App-wide cap on concurrent blob fetches from the diff views. */
76export const blobLimiter = makeLimiter(4);