import type { CommentView } from "#lib/components/comment/comments.ts";
import type { CommitSummary } from "#lib/api/repo.ts";
import type { Revspec } from "#lib/api/pullRoute.ts";
import type { ReactionGroup, Reactor } from "#lib/components/reaction/reactions.ts";
import {
base,
pullAuthor,
pullCollaborator,
sourceRepoDid,
type FixtureFile,
type PullFetchDataset
} from "./pull.ts";
// worst realistic data for the pull page: every value is something a real pull,
// person or repo can carry, spread across rows the way real data mixes it
export type PullDatasetName = "demo" | "worst" | "empty" | "one" | "huge";
export interface PullDataset {
title: string;
body: string;
authorHandle?: string;
targetBranch: string;
sourceBranch: string;
counts: { stars: number; issues: number; pulls: number; forks: number };
commits: CommitSummary[];
versions: (Revspec & { createdAt: string })[];
comments: CommentView[];
reactions: ReactionGroup[];
fetch: PullFetchDataset;
}
const oid = (n: number) =>
Array.from({ length: 5 }, (_, i) =>
((n + 1) * 2_654_435_761 + i * 40_503).toString(16).slice(-8).padStart(8, "0")
).join("");
const changeIdOf = (n: number) =>
Array.from(
{ length: 32 },
(_, i) => "klmnopqrstuvwxyz"[((n + 7) * 31 + i * 17 + i * n) % 16]
).join("");
const longHandle = "aleksandra-wisniewska-kowalczyk.northwind-industries-holdings.example.com";
const jo = { did: "did:plc:jo", handle: "jo.tngl.sh" } satisfies Reactor;
const konstantin = {
did: "did:plc:konstantin",
handle: "konstantin-oberhauser-wettstein.eu.example.org"
} satisfies Reactor;
const unresolved = {
did: "did:plc:ewvi7nxzyoun6zhxrhs64oiz",
handle: "handle.invalid"
} satisfies Reactor;
const targetBranch = "release/2026-q4/hotfix-notification-preferences-migration-rollback";
const sourceBranch =
"aleksandra/feature/notifications-sidebar-unread-state-and-mention-fanout-v2-rebased";
const signOffs = [
"Signed-off-by: Aleksandra Wiśniewska-Kowalczyk <aleksandra@northwind-industries-holdings.example.com>",
"Signed-off-by: Christopher Alexander Montgomery III <chris.montgomery@example.org>",
"Co-authored-by: 王秀英 <xiuying@example.com>",
"Reviewed-by: Jo <jo@example.com>"
].join("\n");
interface CommitSeed {
subject: string;
body?: string;
authorName: string;
authorEmail: string;
authorDid?: string;
authorHandle?: string;
changeId?: boolean;
commentCount?: number;
stat?: { insertions: number; deletions: number };
}
const worstSeeds: CommitSeed[] = [
{
subject:
"appview/pages/templates/repo/pulls/fragments/pullCompareForksBranches.html: keep the fork picker usable when the source repo has thousands of branches",
body: `The picker rendered every branch up front, which locked the tab for several seconds on
large forks.\n\n${signOffs}`,
authorName: "Aleksandra Wiśniewska-Kowalczyk",
authorEmail: "aleksandra@northwind-industries-holdings.example.com",
authorDid: pullAuthor.did,
authorHandle: longHandle,
changeId: true,
commentCount: 12,
stat: { insertions: 1_284, deletions: 967 }
},
{
subject: "Benachrichtigungseinstellungen",
authorName: "Jo",
authorEmail: "jo@example.com",
authorDid: jo.did,
authorHandle: jo.handle,
changeId: true,
commentCount: 1,
stat: { insertions: 1, deletions: 0 }
},
{
subject: "",
authorName: "J",
authorEmail: "a@b.co",
changeId: false,
stat: { insertions: 0, deletions: 0 }
},
{
subject: "pnpm-lock.yaml: bump every dependency",
authorName: "Christopher Alexander Montgomery III",
authorEmail: "bartholomew.fitzgerald@northwind-industries-holdings.example.com",
changeId: false,
stat: { insertions: 128_402, deletions: 97_316 }
},
{
subject: "🦊 fix the <script> escaping in & around **mentions**",
authorName: "🦊 Fox",
authorEmail: "first.last+billing-notifications@example.com",
changeId: true,
commentCount: 3,
stat: { insertions: 42, deletions: 7 }
},
{
subject: "إصلاح ترتيب الإشعارات في الشريط الجانبي",
authorName: "نور الهدى عبد الرحمن",
authorEmail: "nour@example.org",
changeId: true,
stat: { insertions: 18, deletions: 18 }
},
{
subject: "Đặng Thị Ngọc Hân: sửa lỗi hiển thị thông báo",
authorName: "Đặng Thị Ngọc Hân",
authorEmail: "han@example.com",
changeId: true,
stat: { insertions: 3, deletions: 1 }
},
{
subject: "wip",
authorName: konstantin.handle,
authorEmail: "konstantin@example.org",
authorDid: konstantin.did,
authorHandle: konstantin.handle,
changeId: true
}
];
const commitsOf = (seeds: CommitSeed[], count = seeds.length): CommitSummary[] =>
Array.from({ length: count }, (_, index) => {
const seed = seeds[index % seeds.length]!;
const hash = oid(index);
return {
hash,
shortHash: hash.slice(0, 8),
subject: seed.subject,
body: seed.body ?? "",
authorName: seed.authorName,
authorEmail: seed.authorEmail,
authorDid: seed.authorDid,
authorHandle: seed.authorHandle,
when: new Date(Date.UTC(2026, 9, 21, 12) - index * 3_600_000).toISOString(),
parent: index === count - 1 ? base : oid(index + 1),
changeId: seed.changeId === false ? undefined : changeIdOf(index),
commentCount: seed.commentCount ?? 0,
stat: seed.stat
};
});
// each version brings one more commit, the newest on top
const versionsOf = (commits: CommitSummary[], count: number) =>
Array.from({ length: count }, (_, index) => ({
base,
head: commits[Math.max(count - 1 - index, 0)]!.hash,
createdAt: new Date(Date.UTC(2026, 9, 1, 9) + index * 86_400_000).toISOString()
}));
const commentUri = (did: string, rkey: string) => `at://${did}/sh.tangled.feed.comment/${rkey}`;
const reactors = (count: number): Reactor[] =>
Array.from({ length: Math.min(count, 3) }, (_, index) => ({
did: `did:plc:reactor${index}`,
handle: `reactor-${index}.example.com`
}));
const everyReaction = (scale: number): ReactionGroup[] =>
(["👍", "👎", "😆", "🎉", "🫤", "❤️", "🚀", "👀"] as const).map((kind, index) => ({
kind,
count: Math.max(1, Math.round(scale / (index + 1))),
users: reactors(scale),
isReacted: index === 0
}));
const longUrl =
"https://example.com/workspaces/acme/projects/q3-launch/docs/9f8e7d6c5b4a?tab=comments&filter=unresolved&highlight=notification-preferences-migration";
interface CommentSeed {
who: Reactor;
body: string;
pin?: number;
editedAt?: string;
deleted?: boolean;
reactions?: ReactionGroup[];
createdAt?: string;
}
const worstCommentSeeds = (commits: CommitSummary[]): CommentSeed[] => [
{
who: konstantin,
body: `See ${longUrl}
\`\`\`
const preferences = await db.select().from(notificationPreferences).where(and(eq(notificationPreferences.did, did), eq(notificationPreferences.kind, "mention"), isNull(notificationPreferences.deletedAt)));
\`\`\``,
pin: 0,
reactions: everyReaction(1_284)
},
{ who: jo, body: "lgtm" },
{
who: unresolved,
body: "<script>alert(1)</script> & **this is not bold**\nline two of a plain comment"
},
{
who: { did: pullAuthor.did, handle: longHandle },
body: "Benachrichtigungseinstellungsverwaltungsoberfläche",
editedAt: "2026-10-21T08:00:00Z"
},
{
who: konstantin,
body: "نعم، هذا يحل المشكلة في الشريط الجانبي. شكراً!",
pin: 4
},
{ who: jo, body: "", deleted: true },
{
who: { did: pullCollaborator.did, handle: pullCollaborator.handle },
body: "Đặng Thị Ngọc Hân đã xem xét thay đổi này.",
// a commenter with a fast clock
createdAt: new Date(Date.now() + 3 * 60_000).toISOString()
},
{
who: { did: pullAuthor.did, handle: longHandle },
body: `| file | before | after | p99 render (ms) | notes |
| --- | --- | --- | --- | --- |
| \`src/lib/components/notifications/NotificationPreferencesMigrationRollback.svelte\` | 1,284 | 12 | 0.30000000000000004 | first paint only |`,
pin: commits.length - 1
}
];
const commentsOf = (seeds: CommentSeed[], commits: CommitSummary[], count = seeds.length) =>
Array.from({ length: count }, (_, index): CommentView => {
const seed = seeds[index % seeds.length]!;
const rkey = `3mw${index.toString(36).padStart(6, "0")}`;
const pinned = seed.pin === undefined ? undefined : commits[seed.pin % commits.length];
return {
uri: commentUri(seed.who.did, rkey),
rkey,
authorDid: seed.who.did,
authorHandle: seed.who.handle,
createdAt:
seed.createdAt ??
new Date(Date.UTC(2026, 9, 2, 9) + index * 7_200_000).toISOString(),
editedAt: seed.editedAt,
body: seed.body,
deleted: seed.deleted,
reactions: seed.reactions,
embed: pinned
? ({ repo: sourceRepoDid, commit: pinned.hash } as CommentView["embed"])
: undefined
};
});
const deepPath =
"packages/notifications/src/lib/components/notifications/preferences/migrations/2026-10-21T120000Z_rollback_notification_preferences_mention_fanout.ts";
const minified = `export const a=${JSON.stringify(Array.from({ length: 60 }, (_, i) => `notification-preference-key-${i}`))};\n`;
const worstFiles: FixtureFile[] = [
{
path: deepPath,
row: 0,
old: "export const up = () => {};\n",
next: "export const up = () => {};\nexport const down = () => {};\n"
},
{
path: "src/lib/components/notifications/NotificationPreferencesMigrationRollbackConfirmationDialog.svelte",
oldPath: "src/lib/components/notifications/NotificationPreferencesRollbackDialog.svelte",
row: 1,
old: "<dialog></dialog>\n",
next: "<dialog open></dialog>\n"
},
{ path: "static/IMG_20250914_183022_HDR_portrait_edited_edited.HEIC", row: 2, binary: true },
{ path: "build/notifications.min.js", row: 3, old: "", next: minified },
{ path: "src/lib/legacy/unread.ts", row: 4, old: "export const unread = 0;\n" }
];
const conflictPaths = (count: number) =>
Array.from(
{ length: count },
(_, index) =>
[
deepPath,
"appview/pages/templates/repo/pulls/fragments/pullCompareForksBranches.html",
"src/lib/components/notifications/NotificationPreferencesMigrationRollbackConfirmationDialog.svelte",
"README.md",
"docs/ストーリー/通知設定.md"
][index % 5]! + (index < 5 ? "" : `.${index}`)
).map((filename) => ({ filename, reason: "both modified" }));
const body = `Notifications land in the sidebar, and a visit marks them read. The full design discussion is at ${longUrl} and the tracking issue is at https://tangled.example.com/@${longHandle}/core/issues/1284.
\`\`\`
pnpm --filter @tangled/web exec vitest --run --project server src/lib/components/notifications/NotificationPreferencesMigrationRollback.test.ts
\`\`\`
| key | before | after |
| --- | --- | --- |
| \`notificationPreferences.mentionFanoutRollbackEnabled\` | true | false |
${"Lorem ipsum is not what people paste; they paste stack traces. ".repeat(4)}`;
const worstCommits = commitsOf(worstSeeds, 40);
const worstVersions = versionsOf(worstCommits, 14);
const worst: PullDataset = {
title: "appview/pages/templates/repo/pulls/fragments/pullCompareForksBranches.html: keep the fork picker usable when the source repo has thousands of branches and the target branch name is long",
body,
authorHandle: longHandle,
targetBranch,
sourceBranch,
counts: { stars: 12_847, issues: 1_284, pulls: 10_402, forks: 1 },
commits: worstCommits,
versions: worstVersions,
comments: commentsOf(worstCommentSeeds(worstCommits), worstCommits, 16),
reactions: everyReaction(1_284),
fetch: {
commits: worstCommits,
head: worstVersions.at(-1)!.head,
files: worstFiles,
conflicts: conflictPaths(23)
}
};
const oneCommits = commitsOf([worstSeeds[1]!]);
const oneVersions = versionsOf(oneCommits, 1);
const one: PullDataset = {
title: "J",
body: "x",
authorHandle: jo.handle,
targetBranch: "a",
sourceBranch: "b",
counts: { stars: 1, issues: 1, pulls: 1, forks: 1 },
commits: oneCommits,
versions: oneVersions,
comments: commentsOf([{ who: jo, body: "ok", pin: 0 }], oneCommits),
reactions: [{ kind: "👍", count: 1, users: reactors(1), isReacted: false }],
fetch: {
commits: oneCommits,
head: oneVersions[0]!.head,
files: [worstFiles[0]!],
conflicts: conflictPaths(1)
}
};
const empty: PullDataset = {
title: "",
body: "",
authorHandle: undefined,
targetBranch: "main",
sourceBranch: "main",
counts: { stars: 0, issues: 0, pulls: 0, forks: 0 },
commits: [],
versions: [],
comments: [],
reactions: [],
fetch: { commits: [], head: base, files: [], conflicts: [] }
};
const hugeCommits = commitsOf(worstSeeds, 1_000);
const hugeVersions = versionsOf(hugeCommits, 60);
const huge: PullDataset = {
...worst,
counts: { stars: 1_284_000, issues: 98_765, pulls: 123_456, forks: 54_321 },
commits: hugeCommits,
versions: hugeVersions,
comments: commentsOf(worstCommentSeeds(hugeCommits), hugeCommits, 400),
fetch: {
commits: hugeCommits,
head: hugeVersions.at(-1)!.head,
files: worstFiles,
conflicts: conflictPaths(412)
}
};
export const pullDatasets: Record<Exclude<PullDatasetName, "demo">, PullDataset> = {
worst,
empty,
one,
huge
};