1import type { PullRequestRef, PullRequestStack, ThreadPullRequestLink } from "@t3tools/contracts";
2
3/** Saved native membership is enough for navigation, but never supplies action head SHAs. */
4export function savedPullRequestStack(
5 links: ReadonlyArray<ThreadPullRequestLink>,
6 reference: PullRequestRef,
7): PullRequestStack | null {
8 const host = reference.host?.toLowerCase();
9 if (!host) return null;
10 const matching = links.filter(
11 (link) =>
12 link.host.toLowerCase() === host &&
13 link.repository.toLowerCase() === reference.repository.toLowerCase(),
14 );
15 const exact = matching.filter((link) => link.number === reference.number);
16 const candidates =
17 exact.length > 0
18 ? exact
19 : matching.filter((link) =>
20 link.stack?.layers.some((layer) => layer.number === reference.number),
21 );
22 const newest = candidates.toSorted(
23 (a, b) =>
24 Date.parse(b.snapshot?.syncedAt ?? b.linkedAt) -
25 Date.parse(a.snapshot?.syncedAt ?? a.linkedAt),
26 )[0];
27 const stack = newest?.stack;
28 if (!stack || !stack.layers.some((layer) => layer.number === reference.number)) return null;
29 return {
30 id: stack.id,
31 number: stack.number,
32 url: stack.url,
33 base: stack.base,
34 layers: stack.layers.map((layer) => {
35 const snapshot = matching
36 .filter((link) => link.number === layer.number)
37 .toSorted(
38 (a, b) =>
39 Date.parse(b.snapshot?.syncedAt ?? b.linkedAt) -
40 Date.parse(a.snapshot?.syncedAt ?? a.linkedAt),
41 )[0]?.snapshot;
42 return {
43 ...layer,
44 ...(snapshot ? { title: snapshot.title, isDraft: snapshot.isDraft } : {}),
45 };
46 }),
47 };
48}
49
50/**
51 * A fresh absence overrides saved membership; failed refreshes preserve available navigation.
52 * A background refresh keeps the last successful lookup fresh, so stack-dependent actions do
53 * not blink out every time the detail revalidates.
54 */
55export function pullRequestStackView(
56 query: {
57 data: PullRequestStack | null;
58 isSuccess: boolean;
59 isPending: boolean;
60 error: string | null;
61 },
62 saved: PullRequestStack | null,
63) {
64 const data = query.isSuccess ? query.data : (query.data ?? saved);
65 return {
66 data,
67 isFresh: query.isSuccess,
68 notice:
69 data === null
70 ? null
71 : query.error
72 ? "Stack data may be stale. We couldn’t refresh it."
73 : !query.isSuccess || query.isPending
74 ? "Refreshing stack… Showing saved data."
75 : null,
76 };
77}