From 6718baafdc121b924bec11f70f4ceaec6502e9ea Mon Sep 17 00:00:00 2001 From: dawn Date: Fri, 23 Oct 2026 01:02:43 +0300 Subject: [PATCH] web/repo: add the forks page that the forks button links to Signed-off-by: dawn --- web/boneyard.config.json | 3 + ...ones-forklist--skeleton-fixture.bones.json | 637 ++++++++++++++++++ web/src/lib/components/profile/pages.test.ts | 75 +++ web/src/lib/components/profile/pages.ts | 31 + .../repo/ForkList.bones.stories.svelte | 28 + web/src/lib/components/repo/ForkList.svelte | 12 + .../routes/[handle]/[repo]/forks/+page.svelte | 29 + web/src/routes/[handle]/[repo]/forks/+page.ts | 32 + .../[handle]/[repo]/forks/forks.test.ts | 85 +++ 9 files changed, 932 insertions(+) create mode 100644 web/src/lib/bones/bones-forklist--skeleton-fixture.bones.json create mode 100644 web/src/lib/components/repo/ForkList.bones.stories.svelte create mode 100644 web/src/lib/components/repo/ForkList.svelte create mode 100644 web/src/routes/[handle]/[repo]/forks/+page.svelte create mode 100644 web/src/routes/[handle]/[repo]/forks/+page.ts create mode 100644 web/src/routes/[handle]/[repo]/forks/forks.test.ts diff --git a/web/boneyard.config.json b/web/boneyard.config.json index bbc2c6def..6643ed871 100644 --- a/web/boneyard.config.json +++ b/web/boneyard.config.json @@ -4,6 +4,9 @@ "wait": 100, "animate": "pulse", "skeletons": { + "bones-forklist--skeleton-fixture": { + "route": "/iframe.html?id=bones-forklist--skeleton-fixture&viewMode=story" + }, "bones-loadingprimitives--count": { "route": "/iframe.html?id=bones-loadingprimitives--count&viewMode=story" }, diff --git a/web/src/lib/bones/bones-forklist--skeleton-fixture.bones.json b/web/src/lib/bones/bones-forklist--skeleton-fixture.bones.json new file mode 100644 index 000000000..8a725bd26 --- /dev/null +++ b/web/src/lib/bones/bones-forklist--skeleton-fixture.bones.json @@ -0,0 +1,637 @@ +{ + "breakpoints": { + "375": { + "name": "bones-forklist--skeleton-fixture", + "viewportWidth": 343, + "width": 343, + "height": 3784, + "bones": [ + [0, 0, 100, 60, 8, true, 0, true], + [2.3324, 12, 35.2086, 20, 6], + [2.3324, 36, 34.9718, 15, 6], + [0, 76, 100, 60, 8, true, 0, true], + [2.3324, 88, 37.4863, 20, 6], + [2.3324, 112, 34.9718, 15, 6], + [0, 152, 100, 60, 8, true, 0, true], + [2.3324, 164, 38.484, 20, 6], + [2.3324, 188, 34.9718, 15, 6], + [0, 228, 100, 60, 8, true, 0, true], + [2.3324, 240, 36.6527, 20, 6], + [2.3324, 264, 34.9718, 15, 6], + [0, 304, 100, 60, 8, true, 0, true], + [2.3324, 316, 36.1789, 20, 6], + [2.3324, 340, 34.9718, 15, 6], + [0, 380, 100, 60, 8, true, 0, true], + [2.3324, 392, 68.7546, 20, 6], + [2.3324, 416, 34.9718, 15, 6], + [0, 456, 100, 60, 8, true, 0, true], + [2.3324, 468, 35.2086, 20, 6], + [2.3324, 492, 34.9718, 15, 6], + [0, 532, 100, 60, 8, true, 0, true], + [2.3324, 544, 37.4863, 20, 6], + [2.3324, 568, 34.9718, 15, 6], + [0, 608, 100, 60, 8, true, 0, true], + [2.3324, 620, 38.484, 20, 6], + [2.3324, 644, 34.9718, 15, 6], + [0, 684, 100, 60, 8, true, 0, true], + [2.3324, 696, 36.6527, 20, 6], + [2.3324, 720, 34.9718, 15, 6], + [0, 760, 100, 60, 8, true, 0, true], + [2.3324, 772, 36.1789, 20, 6], + [2.3324, 796, 34.9718, 15, 6], + [0, 836, 100, 60, 8, true, 0, true], + [2.3324, 848, 68.7546, 20, 6], + [2.3324, 872, 34.9718, 15, 6], + [0, 912, 100, 60, 8, true, 0, true], + [2.3324, 924, 35.2086, 20, 6], + [2.3324, 948, 34.9718, 15, 6], + [0, 988, 100, 60, 8, true, 0, true], + [2.3324, 1000, 37.4863, 20, 6], + [2.3324, 1024, 34.9718, 15, 6], + [0, 1064, 100, 60, 8, true, 0, true], + [2.3324, 1076, 38.484, 20, 6], + [2.3324, 1100, 34.9718, 15, 6], + [0, 1140, 100, 60, 8, true, 0, true], + [2.3324, 1152, 36.6527, 20, 6], + [2.3324, 1176, 34.9718, 15, 6], + [0, 1216, 100, 60, 8, true, 0, true], + [2.3324, 1228, 36.1789, 20, 6], + [2.3324, 1252, 34.9718, 15, 6], + [0, 1292, 100, 60, 8, true, 0, true], + [2.3324, 1304, 68.7546, 20, 6], + [2.3324, 1328, 34.9718, 15, 6], + [0, 1368, 100, 60, 8, true, 0, true], + [2.3324, 1380, 35.2086, 20, 6], + [2.3324, 1404, 34.9718, 15, 6], + [0, 1444, 100, 60, 8, true, 0, true], + [2.3324, 1456, 37.4863, 20, 6], + [2.3324, 1480, 34.9718, 15, 6], + [0, 1520, 100, 60, 8, true, 0, true], + [2.3324, 1532, 38.484, 20, 6], + [2.3324, 1556, 34.9718, 15, 6], + [0, 1596, 100, 60, 8, true, 0, true], + [2.3324, 1608, 36.6527, 20, 6], + [2.3324, 1632, 34.9718, 15, 6], + [0, 1672, 100, 60, 8, true, 0, true], + [2.3324, 1684, 36.1789, 20, 6], + [2.3324, 1708, 34.9718, 15, 6], + [0, 1748, 100, 60, 8, true, 0, true], + [2.3324, 1760, 68.7546, 20, 6], + [2.3324, 1784, 34.9718, 15, 6], + [0, 1824, 100, 60, 8, true, 0, true], + [2.3324, 1836, 35.2086, 20, 6], + [2.3324, 1860, 34.9718, 15, 6], + [0, 1900, 100, 60, 8, true, 0, true], + [2.3324, 1912, 37.4863, 20, 6], + [2.3324, 1936, 34.9718, 15, 6], + [0, 1976, 100, 60, 8, true, 0, true], + [2.3324, 1988, 38.484, 20, 6], + [2.3324, 2012, 34.9718, 15, 6], + [0, 2052, 100, 60, 8, true, 0, true], + [2.3324, 2064, 36.6527, 20, 6], + [2.3324, 2088, 34.9718, 15, 6], + [0, 2128, 100, 60, 8, true, 0, true], + [2.3324, 2140, 36.1789, 20, 6], + [2.3324, 2164, 34.9718, 15, 6], + [0, 2204, 100, 60, 8, true, 0, true], + [2.3324, 2216, 68.7546, 20, 6], + [2.3324, 2240, 34.9718, 15, 6], + [0, 2280, 100, 60, 8, true, 0, true], + [2.3324, 2292, 35.2086, 20, 6], + [2.3324, 2316, 34.9718, 15, 6], + [0, 2356, 100, 60, 8, true, 0, true], + [2.3324, 2368, 37.4863, 20, 6], + [2.3324, 2392, 34.9718, 15, 6], + [0, 2432, 100, 60, 8, true, 0, true], + [2.3324, 2444, 38.484, 20, 6], + [2.3324, 2468, 34.9718, 15, 6], + [0, 2508, 100, 60, 8, true, 0, true], + [2.3324, 2520, 36.6527, 20, 6], + [2.3324, 2544, 34.9718, 15, 6], + [0, 2584, 100, 60, 8, true, 0, true], + [2.3324, 2596, 36.1789, 20, 6], + [2.3324, 2620, 34.9718, 15, 6], + [0, 2660, 100, 60, 8, true, 0, true], + [2.3324, 2672, 68.7546, 20, 6], + [2.3324, 2696, 34.9718, 15, 6], + [0, 2736, 100, 60, 8, true, 0, true], + [2.3324, 2748, 35.2086, 20, 6], + [2.3324, 2772, 34.9718, 15, 6], + [0, 2812, 100, 60, 8, true, 0, true], + [2.3324, 2824, 37.4863, 20, 6], + [2.3324, 2848, 34.9718, 15, 6], + [0, 2888, 100, 60, 8, true, 0, true], + [2.3324, 2900, 38.484, 20, 6], + [2.3324, 2924, 34.9718, 15, 6], + [0, 2964, 100, 60, 8, true, 0, true], + [2.3324, 2976, 36.6527, 20, 6], + [2.3324, 3000, 34.9718, 15, 6], + [0, 3040, 100, 60, 8, true, 0, true], + [2.3324, 3052, 36.1789, 20, 6], + [2.3324, 3076, 34.9718, 15, 6], + [0, 3116, 100, 60, 8, true, 0, true], + [2.3324, 3128, 68.7546, 20, 6], + [2.3324, 3152, 34.9718, 15, 6], + [0, 3192, 100, 60, 8, true, 0, true], + [2.3324, 3204, 35.2086, 20, 6], + [2.3324, 3228, 34.9718, 15, 6], + [0, 3268, 100, 60, 8, true, 0, true], + [2.3324, 3280, 37.4863, 20, 6], + [2.3324, 3304, 34.9718, 15, 6], + [0, 3344, 100, 60, 8, true, 0, true], + [2.3324, 3356, 38.484, 20, 6], + [2.3324, 3380, 34.9718, 15, 6], + [0, 3420, 100, 60, 8, true, 0, true], + [2.3324, 3432, 36.6527, 20, 6], + [2.3324, 3456, 34.9718, 15, 6], + [0, 3496, 100, 60, 8, true, 0, true], + [2.3324, 3508, 36.1789, 20, 6], + [2.3324, 3532, 34.9718, 15, 6], + [0, 3572, 100, 60, 8, true, 0, true], + [2.3324, 3584, 68.7546, 20, 6], + [2.3324, 3608, 34.9718, 15, 6], + [0, 3648, 100, 60, 8, true, 0, true], + [2.3324, 3660, 35.2086, 20, 6], + [2.3324, 3684, 34.9718, 15, 6], + [0, 3724, 100, 60, 8, true, 0, true], + [2.3324, 3736, 37.4863, 20, 6], + [2.3324, 3760, 34.9718, 15, 6] + ] + }, + "768": { + "name": "bones-forklist--skeleton-fixture", + "viewportWidth": 736, + "width": 736, + "height": 1884, + "bones": [ + [0, 0, 48.913, 60, 8, true, 0, true], + [1.087, 12, 16.4084, 20, 6], + [1.087, 36, 16.298, 15, 6], + [51.087, 0, 48.913, 60, 8, true, 0, true], + [52.1739, 12, 17.4699, 20, 6], + [52.1739, 36, 16.298, 15, 6], + [0, 76, 48.913, 60, 8, true, 0, true], + [1.087, 88, 17.9348, 20, 6], + [1.087, 112, 16.298, 15, 6], + [51.087, 76, 48.913, 60, 8, true, 0, true], + [52.1739, 88, 17.0814, 20, 6], + [52.1739, 112, 16.298, 15, 6], + [0, 152, 48.913, 60, 8, true, 0, true], + [1.087, 164, 16.8606, 20, 6], + [1.087, 188, 16.298, 15, 6], + [51.087, 152, 48.913, 60, 8, true, 0, true], + [52.1739, 164, 32.0419, 20, 6], + [52.1739, 188, 16.298, 15, 6], + [0, 228, 48.913, 60, 8, true, 0, true], + [1.087, 240, 16.4084, 20, 6], + [1.087, 264, 16.298, 15, 6], + [51.087, 228, 48.913, 60, 8, true, 0, true], + [52.1739, 240, 17.4699, 20, 6], + [52.1739, 264, 16.298, 15, 6], + [0, 304, 48.913, 60, 8, true, 0, true], + [1.087, 316, 17.9348, 20, 6], + [1.087, 340, 16.298, 15, 6], + [51.087, 304, 48.913, 60, 8, true, 0, true], + [52.1739, 316, 17.0814, 20, 6], + [52.1739, 340, 16.298, 15, 6], + [0, 380, 48.913, 60, 8, true, 0, true], + [1.087, 392, 16.8606, 20, 6], + [1.087, 416, 16.298, 15, 6], + [51.087, 380, 48.913, 60, 8, true, 0, true], + [52.1739, 392, 32.0419, 20, 6], + [52.1739, 416, 16.298, 15, 6], + [0, 456, 48.913, 60, 8, true, 0, true], + [1.087, 468, 16.4084, 20, 6], + [1.087, 492, 16.298, 15, 6], + [51.087, 456, 48.913, 60, 8, true, 0, true], + [52.1739, 468, 17.4699, 20, 6], + [52.1739, 492, 16.298, 15, 6], + [0, 532, 48.913, 60, 8, true, 0, true], + [1.087, 544, 17.9348, 20, 6], + [1.087, 568, 16.298, 15, 6], + [51.087, 532, 48.913, 60, 8, true, 0, true], + [52.1739, 544, 17.0814, 20, 6], + [52.1739, 568, 16.298, 15, 6], + [0, 608, 48.913, 60, 8, true, 0, true], + [1.087, 620, 16.8606, 20, 6], + [1.087, 644, 16.298, 15, 6], + [51.087, 608, 48.913, 60, 8, true, 0, true], + [52.1739, 620, 32.0419, 20, 6], + [52.1739, 644, 16.298, 15, 6], + [0, 684, 48.913, 60, 8, true, 0, true], + [1.087, 696, 16.4084, 20, 6], + [1.087, 720, 16.298, 15, 6], + [51.087, 684, 48.913, 60, 8, true, 0, true], + [52.1739, 696, 17.4699, 20, 6], + [52.1739, 720, 16.298, 15, 6], + [0, 760, 48.913, 60, 8, true, 0, true], + [1.087, 772, 17.9348, 20, 6], + [1.087, 796, 16.298, 15, 6], + [51.087, 760, 48.913, 60, 8, true, 0, true], + [52.1739, 772, 17.0814, 20, 6], + [52.1739, 796, 16.298, 15, 6], + [0, 836, 48.913, 60, 8, true, 0, true], + [1.087, 848, 16.8606, 20, 6], + [1.087, 872, 16.298, 15, 6], + [51.087, 836, 48.913, 60, 8, true, 0, true], + [52.1739, 848, 32.0419, 20, 6], + [52.1739, 872, 16.298, 15, 6], + [0, 912, 48.913, 60, 8, true, 0, true], + [1.087, 924, 16.4084, 20, 6], + [1.087, 948, 16.298, 15, 6], + [51.087, 912, 48.913, 60, 8, true, 0, true], + [52.1739, 924, 17.4699, 20, 6], + [52.1739, 948, 16.298, 15, 6], + [0, 988, 48.913, 60, 8, true, 0, true], + [1.087, 1000, 17.9348, 20, 6], + [1.087, 1024, 16.298, 15, 6], + [51.087, 988, 48.913, 60, 8, true, 0, true], + [52.1739, 1000, 17.0814, 20, 6], + [52.1739, 1024, 16.298, 15, 6], + [0, 1064, 48.913, 60, 8, true, 0, true], + [1.087, 1076, 16.8606, 20, 6], + [1.087, 1100, 16.298, 15, 6], + [51.087, 1064, 48.913, 60, 8, true, 0, true], + [52.1739, 1076, 32.0419, 20, 6], + [52.1739, 1100, 16.298, 15, 6], + [0, 1140, 48.913, 60, 8, true, 0, true], + [1.087, 1152, 16.4084, 20, 6], + [1.087, 1176, 16.298, 15, 6], + [51.087, 1140, 48.913, 60, 8, true, 0, true], + [52.1739, 1152, 17.4699, 20, 6], + [52.1739, 1176, 16.298, 15, 6], + [0, 1216, 48.913, 60, 8, true, 0, true], + [1.087, 1228, 17.9348, 20, 6], + [1.087, 1252, 16.298, 15, 6], + [51.087, 1216, 48.913, 60, 8, true, 0, true], + [52.1739, 1228, 17.0814, 20, 6], + [52.1739, 1252, 16.298, 15, 6], + [0, 1292, 48.913, 60, 8, true, 0, true], + [1.087, 1304, 16.8606, 20, 6], + [1.087, 1328, 16.298, 15, 6], + [51.087, 1292, 48.913, 60, 8, true, 0, true], + [52.1739, 1304, 32.0419, 20, 6], + [52.1739, 1328, 16.298, 15, 6], + [0, 1368, 48.913, 60, 8, true, 0, true], + [1.087, 1380, 16.4084, 20, 6], + [1.087, 1404, 16.298, 15, 6], + [51.087, 1368, 48.913, 60, 8, true, 0, true], + [52.1739, 1380, 17.4699, 20, 6], + [52.1739, 1404, 16.298, 15, 6], + [0, 1444, 48.913, 60, 8, true, 0, true], + [1.087, 1456, 17.9348, 20, 6], + [1.087, 1480, 16.298, 15, 6], + [51.087, 1444, 48.913, 60, 8, true, 0, true], + [52.1739, 1456, 17.0814, 20, 6], + [52.1739, 1480, 16.298, 15, 6], + [0, 1520, 48.913, 60, 8, true, 0, true], + [1.087, 1532, 16.8606, 20, 6], + [1.087, 1556, 16.298, 15, 6], + [51.087, 1520, 48.913, 60, 8, true, 0, true], + [52.1739, 1532, 32.0419, 20, 6], + [52.1739, 1556, 16.298, 15, 6], + [0, 1596, 48.913, 60, 8, true, 0, true], + [1.087, 1608, 16.4084, 20, 6], + [1.087, 1632, 16.298, 15, 6], + [51.087, 1596, 48.913, 60, 8, true, 0, true], + [52.1739, 1608, 17.4699, 20, 6], + [52.1739, 1632, 16.298, 15, 6], + [0, 1672, 48.913, 60, 8, true, 0, true], + [1.087, 1684, 17.9348, 20, 6], + [1.087, 1708, 16.298, 15, 6], + [51.087, 1672, 48.913, 60, 8, true, 0, true], + [52.1739, 1684, 17.0814, 20, 6], + [52.1739, 1708, 16.298, 15, 6], + [0, 1748, 48.913, 60, 8, true, 0, true], + [1.087, 1760, 16.8606, 20, 6], + [1.087, 1784, 16.298, 15, 6], + [51.087, 1748, 48.913, 60, 8, true, 0, true], + [52.1739, 1760, 32.0419, 20, 6], + [52.1739, 1784, 16.298, 15, 6], + [0, 1824, 48.913, 60, 8, true, 0, true], + [1.087, 1836, 16.4084, 20, 6], + [1.087, 1860, 16.298, 15, 6], + [51.087, 1824, 48.913, 60, 8, true, 0, true], + [52.1739, 1836, 17.4699, 20, 6], + [52.1739, 1860, 16.298, 15, 6] + ] + }, + "1024": { + "name": "bones-forklist--skeleton-fixture", + "viewportWidth": 992, + "width": 992, + "height": 1276, + "bones": [ + [0, 0, 32.2581, 60, 8, true, 0, true], + [0.8065, 12, 12.174, 20, 6], + [0.8065, 36, 12.092, 15, 6], + [33.871, 0, 32.2581, 60, 8, true, 0, true], + [34.6774, 12, 12.9615, 20, 6], + [34.6774, 36, 12.092, 15, 6], + [67.7419, 0, 32.2581, 60, 8, true, 0, true], + [68.5484, 12, 13.3065, 20, 6], + [68.5484, 36, 12.092, 15, 6], + [0, 76, 32.2581, 60, 8, true, 0, true], + [0.8065, 88, 12.6733, 20, 6], + [0.8065, 112, 12.092, 15, 6], + [33.871, 76, 32.2581, 60, 8, true, 0, true], + [34.6774, 88, 12.5095, 20, 6], + [34.6774, 112, 12.092, 15, 6], + [67.7419, 76, 32.2581, 60, 8, true, 0, true], + [68.5484, 88, 23.773, 20, 6], + [68.5484, 112, 12.092, 15, 6], + [0, 152, 32.2581, 60, 8, true, 0, true], + [0.8065, 164, 12.174, 20, 6], + [0.8065, 188, 12.092, 15, 6], + [33.871, 152, 32.2581, 60, 8, true, 0, true], + [34.6774, 164, 12.9615, 20, 6], + [34.6774, 188, 12.092, 15, 6], + [67.7419, 152, 32.2581, 60, 8, true, 0, true], + [68.5484, 164, 13.3065, 20, 6], + [68.5484, 188, 12.092, 15, 6], + [0, 228, 32.2581, 60, 8, true, 0, true], + [0.8065, 240, 12.6733, 20, 6], + [0.8065, 264, 12.092, 15, 6], + [33.871, 228, 32.2581, 60, 8, true, 0, true], + [34.6774, 240, 12.5095, 20, 6], + [34.6774, 264, 12.092, 15, 6], + [67.7419, 228, 32.2581, 60, 8, true, 0, true], + [68.5484, 240, 23.773, 20, 6], + [68.5484, 264, 12.092, 15, 6], + [0, 304, 32.2581, 60, 8, true, 0, true], + [0.8065, 316, 12.174, 20, 6], + [0.8065, 340, 12.092, 15, 6], + [33.871, 304, 32.2581, 60, 8, true, 0, true], + [34.6774, 316, 12.9615, 20, 6], + [34.6774, 340, 12.092, 15, 6], + [67.7419, 304, 32.2581, 60, 8, true, 0, true], + [68.5484, 316, 13.3065, 20, 6], + [68.5484, 340, 12.092, 15, 6], + [0, 380, 32.2581, 60, 8, true, 0, true], + [0.8065, 392, 12.6733, 20, 6], + [0.8065, 416, 12.092, 15, 6], + [33.871, 380, 32.2581, 60, 8, true, 0, true], + [34.6774, 392, 12.5095, 20, 6], + [34.6774, 416, 12.092, 15, 6], + [67.7419, 380, 32.2581, 60, 8, true, 0, true], + [68.5484, 392, 23.773, 20, 6], + [68.5484, 416, 12.092, 15, 6], + [0, 456, 32.2581, 60, 8, true, 0, true], + [0.8065, 468, 12.174, 20, 6], + [0.8065, 492, 12.092, 15, 6], + [33.871, 456, 32.2581, 60, 8, true, 0, true], + [34.6774, 468, 12.9615, 20, 6], + [34.6774, 492, 12.092, 15, 6], + [67.7419, 456, 32.2581, 60, 8, true, 0, true], + [68.5484, 468, 13.3065, 20, 6], + [68.5484, 492, 12.092, 15, 6], + [0, 532, 32.2581, 60, 8, true, 0, true], + [0.8065, 544, 12.6733, 20, 6], + [0.8065, 568, 12.092, 15, 6], + [33.871, 532, 32.2581, 60, 8, true, 0, true], + [34.6774, 544, 12.5095, 20, 6], + [34.6774, 568, 12.092, 15, 6], + [67.7419, 532, 32.2581, 60, 8, true, 0, true], + [68.5484, 544, 23.773, 20, 6], + [68.5484, 568, 12.092, 15, 6], + [0, 608, 32.2581, 60, 8, true, 0, true], + [0.8065, 620, 12.174, 20, 6], + [0.8065, 644, 12.092, 15, 6], + [33.871, 608, 32.2581, 60, 8, true, 0, true], + [34.6774, 620, 12.9615, 20, 6], + [34.6774, 644, 12.092, 15, 6], + [67.7419, 608, 32.2581, 60, 8, true, 0, true], + [68.5484, 620, 13.3065, 20, 6], + [68.5484, 644, 12.092, 15, 6], + [0, 684, 32.2581, 60, 8, true, 0, true], + [0.8065, 696, 12.6733, 20, 6], + [0.8065, 720, 12.092, 15, 6], + [33.871, 684, 32.2581, 60, 8, true, 0, true], + [34.6774, 696, 12.5095, 20, 6], + [34.6774, 720, 12.092, 15, 6], + [67.7419, 684, 32.2581, 60, 8, true, 0, true], + [68.5484, 696, 23.773, 20, 6], + [68.5484, 720, 12.092, 15, 6], + [0, 760, 32.2581, 60, 8, true, 0, true], + [0.8065, 772, 12.174, 20, 6], + [0.8065, 796, 12.092, 15, 6], + [33.871, 760, 32.2581, 60, 8, true, 0, true], + [34.6774, 772, 12.9615, 20, 6], + [34.6774, 796, 12.092, 15, 6], + [67.7419, 760, 32.2581, 60, 8, true, 0, true], + [68.5484, 772, 13.3065, 20, 6], + [68.5484, 796, 12.092, 15, 6], + [0, 836, 32.2581, 60, 8, true, 0, true], + [0.8065, 848, 12.6733, 20, 6], + [0.8065, 872, 12.092, 15, 6], + [33.871, 836, 32.2581, 60, 8, true, 0, true], + [34.6774, 848, 12.5095, 20, 6], + [34.6774, 872, 12.092, 15, 6], + [67.7419, 836, 32.2581, 60, 8, true, 0, true], + [68.5484, 848, 23.773, 20, 6], + [68.5484, 872, 12.092, 15, 6], + [0, 912, 32.2581, 60, 8, true, 0, true], + [0.8065, 924, 12.174, 20, 6], + [0.8065, 948, 12.092, 15, 6], + [33.871, 912, 32.2581, 60, 8, true, 0, true], + [34.6774, 924, 12.9615, 20, 6], + [34.6774, 948, 12.092, 15, 6], + [67.7419, 912, 32.2581, 60, 8, true, 0, true], + [68.5484, 924, 13.3065, 20, 6], + [68.5484, 948, 12.092, 15, 6], + [0, 988, 32.2581, 60, 8, true, 0, true], + [0.8065, 1000, 12.6733, 20, 6], + [0.8065, 1024, 12.092, 15, 6], + [33.871, 988, 32.2581, 60, 8, true, 0, true], + [34.6774, 1000, 12.5095, 20, 6], + [34.6774, 1024, 12.092, 15, 6], + [67.7419, 988, 32.2581, 60, 8, true, 0, true], + [68.5484, 1000, 23.773, 20, 6], + [68.5484, 1024, 12.092, 15, 6], + [0, 1064, 32.2581, 60, 8, true, 0, true], + [0.8065, 1076, 12.174, 20, 6], + [0.8065, 1100, 12.092, 15, 6], + [33.871, 1064, 32.2581, 60, 8, true, 0, true], + [34.6774, 1076, 12.9615, 20, 6], + [34.6774, 1100, 12.092, 15, 6], + [67.7419, 1064, 32.2581, 60, 8, true, 0, true], + [68.5484, 1076, 13.3065, 20, 6], + [68.5484, 1100, 12.092, 15, 6], + [0, 1140, 32.2581, 60, 8, true, 0, true], + [0.8065, 1152, 12.6733, 20, 6], + [0.8065, 1176, 12.092, 15, 6], + [33.871, 1140, 32.2581, 60, 8, true, 0, true], + [34.6774, 1152, 12.5095, 20, 6], + [34.6774, 1176, 12.092, 15, 6], + [67.7419, 1140, 32.2581, 60, 8, true, 0, true], + [68.5484, 1152, 23.773, 20, 6], + [68.5484, 1176, 12.092, 15, 6], + [0, 1216, 32.2581, 60, 8, true, 0, true], + [0.8065, 1228, 12.174, 20, 6], + [0.8065, 1252, 12.092, 15, 6], + [33.871, 1216, 32.2581, 60, 8, true, 0, true], + [34.6774, 1228, 12.9615, 20, 6], + [34.6774, 1252, 12.092, 15, 6] + ] + }, + "1440": { + "name": "bones-forklist--skeleton-fixture", + "viewportWidth": 1408, + "width": 1408, + "height": 1276, + "bones": [ + [0, 0, 32.575, 60, 8, true, 0, true], + [0.5682, 12, 8.5771, 20, 6], + [0.5682, 36, 8.5194, 15, 6], + [33.7114, 0, 32.5761, 60, 8, true, 0, true], + [34.2796, 12, 9.132, 20, 6], + [34.2796, 36, 8.5194, 15, 6], + [67.4239, 0, 32.575, 60, 8, true, 0, true], + [67.9921, 12, 9.375, 20, 6], + [67.9921, 36, 8.5194, 15, 6], + [0, 76, 32.575, 60, 8, true, 0, true], + [0.5682, 88, 8.9289, 20, 6], + [0.5682, 112, 8.5194, 15, 6], + [33.7114, 76, 32.5761, 60, 8, true, 0, true], + [34.2796, 88, 8.8135, 20, 6], + [34.2796, 112, 8.5194, 15, 6], + [67.4239, 76, 32.575, 60, 8, true, 0, true], + [67.9921, 88, 16.7492, 20, 6], + [67.9921, 112, 8.5194, 15, 6], + [0, 152, 32.575, 60, 8, true, 0, true], + [0.5682, 164, 8.5771, 20, 6], + [0.5682, 188, 8.5194, 15, 6], + [33.7114, 152, 32.5761, 60, 8, true, 0, true], + [34.2796, 164, 9.132, 20, 6], + [34.2796, 188, 8.5194, 15, 6], + [67.4239, 152, 32.575, 60, 8, true, 0, true], + [67.9921, 164, 9.375, 20, 6], + [67.9921, 188, 8.5194, 15, 6], + [0, 228, 32.575, 60, 8, true, 0, true], + [0.5682, 240, 8.9289, 20, 6], + [0.5682, 264, 8.5194, 15, 6], + [33.7114, 228, 32.5761, 60, 8, true, 0, true], + [34.2796, 240, 8.8135, 20, 6], + [34.2796, 264, 8.5194, 15, 6], + [67.4239, 228, 32.575, 60, 8, true, 0, true], + [67.9921, 240, 16.7492, 20, 6], + [67.9921, 264, 8.5194, 15, 6], + [0, 304, 32.575, 60, 8, true, 0, true], + [0.5682, 316, 8.5771, 20, 6], + [0.5682, 340, 8.5194, 15, 6], + [33.7114, 304, 32.5761, 60, 8, true, 0, true], + [34.2796, 316, 9.132, 20, 6], + [34.2796, 340, 8.5194, 15, 6], + [67.4239, 304, 32.575, 60, 8, true, 0, true], + [67.9921, 316, 9.375, 20, 6], + [67.9921, 340, 8.5194, 15, 6], + [0, 380, 32.575, 60, 8, true, 0, true], + [0.5682, 392, 8.9289, 20, 6], + [0.5682, 416, 8.5194, 15, 6], + [33.7114, 380, 32.5761, 60, 8, true, 0, true], + [34.2796, 392, 8.8135, 20, 6], + [34.2796, 416, 8.5194, 15, 6], + [67.4239, 380, 32.575, 60, 8, true, 0, true], + [67.9921, 392, 16.7492, 20, 6], + [67.9921, 416, 8.5194, 15, 6], + [0, 456, 32.575, 60, 8, true, 0, true], + [0.5682, 468, 8.5771, 20, 6], + [0.5682, 492, 8.5194, 15, 6], + [33.7114, 456, 32.5761, 60, 8, true, 0, true], + [34.2796, 468, 9.132, 20, 6], + [34.2796, 492, 8.5194, 15, 6], + [67.4239, 456, 32.575, 60, 8, true, 0, true], + [67.9921, 468, 9.375, 20, 6], + [67.9921, 492, 8.5194, 15, 6], + [0, 532, 32.575, 60, 8, true, 0, true], + [0.5682, 544, 8.9289, 20, 6], + [0.5682, 568, 8.5194, 15, 6], + [33.7114, 532, 32.5761, 60, 8, true, 0, true], + [34.2796, 544, 8.8135, 20, 6], + [34.2796, 568, 8.5194, 15, 6], + [67.4239, 532, 32.575, 60, 8, true, 0, true], + [67.9921, 544, 16.7492, 20, 6], + [67.9921, 568, 8.5194, 15, 6], + [0, 608, 32.575, 60, 8, true, 0, true], + [0.5682, 620, 8.5771, 20, 6], + [0.5682, 644, 8.5194, 15, 6], + [33.7114, 608, 32.5761, 60, 8, true, 0, true], + [34.2796, 620, 9.132, 20, 6], + [34.2796, 644, 8.5194, 15, 6], + [67.4239, 608, 32.575, 60, 8, true, 0, true], + [67.9921, 620, 9.375, 20, 6], + [67.9921, 644, 8.5194, 15, 6], + [0, 684, 32.575, 60, 8, true, 0, true], + [0.5682, 696, 8.9289, 20, 6], + [0.5682, 720, 8.5194, 15, 6], + [33.7114, 684, 32.5761, 60, 8, true, 0, true], + [34.2796, 696, 8.8135, 20, 6], + [34.2796, 720, 8.5194, 15, 6], + [67.4239, 684, 32.575, 60, 8, true, 0, true], + [67.9921, 696, 16.7492, 20, 6], + [67.9921, 720, 8.5194, 15, 6], + [0, 760, 32.575, 60, 8, true, 0, true], + [0.5682, 772, 8.5771, 20, 6], + [0.5682, 796, 8.5194, 15, 6], + [33.7114, 760, 32.5761, 60, 8, true, 0, true], + [34.2796, 772, 9.132, 20, 6], + [34.2796, 796, 8.5194, 15, 6], + [67.4239, 760, 32.575, 60, 8, true, 0, true], + [67.9921, 772, 9.375, 20, 6], + [67.9921, 796, 8.5194, 15, 6], + [0, 836, 32.575, 60, 8, true, 0, true], + [0.5682, 848, 8.9289, 20, 6], + [0.5682, 872, 8.5194, 15, 6], + [33.7114, 836, 32.5761, 60, 8, true, 0, true], + [34.2796, 848, 8.8135, 20, 6], + [34.2796, 872, 8.5194, 15, 6], + [67.4239, 836, 32.575, 60, 8, true, 0, true], + [67.9921, 848, 16.7492, 20, 6], + [67.9921, 872, 8.5194, 15, 6], + [0, 912, 32.575, 60, 8, true, 0, true], + [0.5682, 924, 8.5771, 20, 6], + [0.5682, 948, 8.5194, 15, 6], + [33.7114, 912, 32.5761, 60, 8, true, 0, true], + [34.2796, 924, 9.132, 20, 6], + [34.2796, 948, 8.5194, 15, 6], + [67.4239, 912, 32.575, 60, 8, true, 0, true], + [67.9921, 924, 9.375, 20, 6], + [67.9921, 948, 8.5194, 15, 6], + [0, 988, 32.575, 60, 8, true, 0, true], + [0.5682, 1000, 8.9289, 20, 6], + [0.5682, 1024, 8.5194, 15, 6], + [33.7114, 988, 32.5761, 60, 8, true, 0, true], + [34.2796, 1000, 8.8135, 20, 6], + [34.2796, 1024, 8.5194, 15, 6], + [67.4239, 988, 32.575, 60, 8, true, 0, true], + [67.9921, 1000, 16.7492, 20, 6], + [67.9921, 1024, 8.5194, 15, 6], + [0, 1064, 32.575, 60, 8, true, 0, true], + [0.5682, 1076, 8.5771, 20, 6], + [0.5682, 1100, 8.5194, 15, 6], + [33.7114, 1064, 32.5761, 60, 8, true, 0, true], + [34.2796, 1076, 9.132, 20, 6], + [34.2796, 1100, 8.5194, 15, 6], + [67.4239, 1064, 32.575, 60, 8, true, 0, true], + [67.9921, 1076, 9.375, 20, 6], + [67.9921, 1100, 8.5194, 15, 6], + [0, 1140, 32.575, 60, 8, true, 0, true], + [0.5682, 1152, 8.9289, 20, 6], + [0.5682, 1176, 8.5194, 15, 6], + [33.7114, 1140, 32.5761, 60, 8, true, 0, true], + [34.2796, 1152, 8.8135, 20, 6], + [34.2796, 1176, 8.5194, 15, 6], + [67.4239, 1140, 32.575, 60, 8, true, 0, true], + [67.9921, 1152, 16.7492, 20, 6], + [67.9921, 1176, 8.5194, 15, 6], + [0, 1216, 32.575, 60, 8, true, 0, true], + [0.5682, 1228, 8.5771, 20, 6], + [0.5682, 1252, 8.5194, 15, 6], + [33.7114, 1216, 32.5761, 60, 8, true, 0, true], + [34.2796, 1228, 9.132, 20, 6], + [34.2796, 1252, 8.5194, 15, 6] + ] + } + }, + "_hash": "0db68ae9c82b9bd4224a8c1fbffdf08e" +} diff --git a/web/src/lib/components/profile/pages.test.ts b/web/src/lib/components/profile/pages.test.ts index b7b886320..08614fc63 100644 --- a/web/src/lib/components/profile/pages.test.ts +++ b/web/src/lib/components/profile/pages.test.ts @@ -3,6 +3,7 @@ import { createBobbinClient } from "#lib/api/client.ts"; import { TYPE_MINIDOC, TYPE_REPO_VIEW } from "#lib/api/enrich.ts"; import { fetchPeoplePage, + fetchForksPage, fetchPinned, fetchProfileActivity, fetchReposPage, @@ -498,3 +499,77 @@ describe("fetchProfileActivity", () => { expect(activity.months[2].issues).toMatchObject([{ rkey: "closed", state: "closed" }]); }); }); + +describe("fetchForksPage", () => { + const forkUri = "at://did:plc:fork/org.tangled.repo.manifest/self"; + + it("lists forks of a repo did with the fork owner's handle and counts", async () => { + const fetchMock = vi.fn().mockResolvedValue( + Response.json({ + output: { + items: [ + { + uri: forkUri, + value: { + declaration: { owner: "did:plc:bob", name: "reef" }, + createdAt: "2026-08-01T00:00:00Z" + } + } + ], + cursor: "next", + total: 41 + }, + data: { + "did:plc:bob": { + "org.tangled.repo.manifest:.repo": { + [TYPE_MINIDOC]: { did: "did:plc:bob", handle: "bob.test" } + } + }, + [forkUri]: { + "org.tangled.repo.manifest:.": { + [TYPE_REPO_VIEW]: { + did: "did:plc:fork", + slug: "reef", + owner: { did: "did:plc:bob", handle: "bob.test" }, + createdAt: "2026-08-01T00:00:00Z", + starCount: 2, + forkCount: 0, + openIssueCount: 0, + openPullCount: 0 + } + } + } + } + }) + ); + const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock }); + + const page = await fetchForksPage(ctx, { repoDid: "did:plc:source", offset: 30 }); + + const body = requestBody(fetchMock) as ReturnType & { + params: Record; + }; + expect(body.xrpc).toBe("org.tangled.repo.listForks"); + expect(body.params).toMatchObject({ subject: "did:plc:source", offset: 30 }); + expect(body.enrich.map((descriptor) => descriptor.targets?.[0])).toEqual([ + "items[].uri", + "items[].value.declaration.owner" + ]); + expect(page.total).toBe(41); + expect(page.cursor).toBe("next"); + expect(page.items).toMatchObject([ + { name: "reef", repoDid: "did:plc:fork", ownerHandle: "bob.test", stars: 2 } + ]); + }); + + it("is empty for a repo nobody forked", async () => { + const fetchMock = vi + .fn() + .mockResolvedValue(Response.json({ output: { items: [], total: 0 }, data: {} })); + const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock }); + + const page = await fetchForksPage(ctx, { repoDid: "did:plc:source" }); + + expect(page).toEqual({ items: [], cursor: undefined, total: 0 }); + }); +}); diff --git a/web/src/lib/components/profile/pages.ts b/web/src/lib/components/profile/pages.ts index cd897fabb..978ab3b96 100644 --- a/web/src/lib/components/profile/pages.ts +++ b/web/src/lib/components/profile/pages.ts @@ -298,6 +298,37 @@ export const fetchReposPage = async ( }; }; +export interface ForksPageOptions { + repoDid: string; + viewerDid?: string; + offset?: number; + limit?: number; +} + +export const fetchForksPage = async ( + ctx: BobbinContext, + { repoDid, viewerDid, offset, limit = PROFILE_PAGE_LIMIT }: ForksPageOptions +): Promise & { total?: number }> => { + const enriched = await enrich & { total?: number }>(ctx, { + xrpc: "org.tangled.repo.listForks", + params: { subject: repoDid, limit, offset }, + enrich: [ + target(REPO_VIEW, ["items[].uri"]), + target(REPO_OWNER_DOCS, ["items[].value.declaration.owner"]) + ], + viewer: viewerDid + }); + return { + items: enriched.output.items.map((item) => { + const owner = repoOwnerOf(enriched.data, item); + const handle = owner.handle.endsWith(".invalid") ? owner.did : owner.handle; + return resolveRepoCard(item, handle, enriched.data); + }), + cursor: enriched.output.cursor, + total: enriched.output.total + }; +}; + export interface StringsPageOptions { did: string; handle: string; diff --git a/web/src/lib/components/repo/ForkList.bones.stories.svelte b/web/src/lib/components/repo/ForkList.bones.stories.svelte new file mode 100644 index 000000000..e7a052c81 --- /dev/null +++ b/web/src/lib/components/repo/ForkList.bones.stories.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/web/src/lib/components/repo/ForkList.svelte b/web/src/lib/components/repo/ForkList.svelte new file mode 100644 index 000000000..b521db1f5 --- /dev/null +++ b/web/src/lib/components/repo/ForkList.svelte @@ -0,0 +1,12 @@ + + +
+ {#each forks as repo (repoKey(repo))} + + {/each} +
diff --git a/web/src/routes/[handle]/[repo]/forks/+page.svelte b/web/src/routes/[handle]/[repo]/forks/+page.svelte new file mode 100644 index 000000000..7e6e4bdda --- /dev/null +++ b/web/src/routes/[handle]/[repo]/forks/+page.svelte @@ -0,0 +1,29 @@ + + + + + {#snippet skeleton()} + + + {/snippet} + {@const forks = await data.forks} + {#if forks.length === 0} + + {:else} + + {/if} + + + + diff --git a/web/src/routes/[handle]/[repo]/forks/+page.ts b/web/src/routes/[handle]/[repo]/forks/+page.ts new file mode 100644 index 000000000..df8e608be --- /dev/null +++ b/web/src/routes/[handle]/[repo]/forks/+page.ts @@ -0,0 +1,32 @@ +import { createBobbinClient } from "#lib/api/client.ts"; +import { settle, stream } from "#lib/api/load.ts"; +import { fetchForksPage } from "#lib/components/profile/pages.ts"; +import { offsetFor, pageCount, pageParam } from "#lib/pagination.ts"; +import type { PageLoad } from "./$types"; + +const LIMIT = 50; + +export const load: PageLoad = async (event) => { + const parent = await event.parent(); + const handle = decodeURIComponent(event.params.handle ?? ""); + const name = decodeURIComponent(event.params.repo ?? ""); + const page = pageParam(event.url); + const offset = offsetFor(page, LIMIT); + + const ctx = createBobbinClient({ + serviceUrl: parent.publicConfig.bobbinUrl, + fetch: event.fetch + }); + + const result = Promise.resolve(parent.repo).then(async ({ repoDid }) => { + if (!repoDid) return null; + return fetchForksPage(ctx, { repoDid, offset, limit: LIMIT }).catch(() => null); + }); + + return settle({ + meta: { title: `Forks ยท ${handle}/${name}` }, + page, + pageCount: stream(result.then((res) => pageCount(res?.total ?? 0, LIMIT))), + forks: stream(result.then((res) => res?.items ?? [])) + }); +}; diff --git a/web/src/routes/[handle]/[repo]/forks/forks.test.ts b/web/src/routes/[handle]/[repo]/forks/forks.test.ts new file mode 100644 index 000000000..4e9d67de5 --- /dev/null +++ b/web/src/routes/[handle]/[repo]/forks/forks.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it, vi } from "vitest"; +import { load } from "./+page"; + +const REPO_DID = "did:plc:j5hmlfdrwkvtxm7cjmu7j2is"; + +const fork = (owner: string, name: string) => ({ + uri: `at://did:plc:${name}/org.tangled.repo.manifest/self`, + value: { declaration: { owner, name }, createdAt: "2026-08-01T00:00:00Z" } +}); + +// the load event boiled down to what the loader actually reads +const makeEvent = ({ + search = "", + repoDid = REPO_DID +}: { search?: string; repoDid?: string | null } = {}) => { + const fetchMock = vi.fn(); + return { + fetchMock, + event: { + url: new URL(`http://web.test/tangled.org/core/forks${search}`), + params: { handle: "tangled.org", repo: "core" }, + fetch: fetchMock, + parent: async () => ({ + publicConfig: { bobbinUrl: "https://bobbin.test" }, + repo: { repoDid: repoDid ?? undefined, ownerHandle: "tangled.org", name: "core" } + }) + } as never + }; +}; + +// PageLoad's return type allows void, but this loader always answers +const runLoad = async (event: never) => { + const data = await load(event); + expect(data).toBeDefined(); + return data!; +}; + +const requestParams = (fetchMock: ReturnType) => { + const init = fetchMock.mock.calls[0][1] as RequestInit; + return (JSON.parse(String(init.body)) as { params: Record }).params; +}; + +describe("forks load", () => { + it("lists the first page of forks and counts pages from the total", async () => { + const { event, fetchMock } = makeEvent(); + fetchMock.mockResolvedValue( + Response.json({ + output: { items: [fork("did:plc:bob", "reef")], total: 120 }, + data: {} + }) + ); + + const data = await runLoad(event); + + expect(data.page).toBe(1); + expect(await data.pageCount).toBe(3); + expect(await data.forks).toMatchObject([{ name: "reef", repoDid: "did:plc:reef" }]); + expect(requestParams(fetchMock)).toEqual({ subject: REPO_DID, limit: 50 }); + }); + + it("asks for the page in the url", async () => { + const { event, fetchMock } = makeEvent({ search: "?page=3" }); + fetchMock.mockResolvedValue(Response.json({ output: { items: [], total: 120 }, data: {} })); + + const data = await runLoad(event); + + expect(data.page).toBe(3); + await data.forks; + expect(requestParams(fetchMock)).toEqual({ subject: REPO_DID, limit: 50, offset: 100 }); + }); + + it("is empty without a repo did or when bobbin fails", async () => { + const noDid = makeEvent({ repoDid: null }); + const noDidData = await runLoad(noDid.event); + expect(await noDidData.forks).toEqual([]); + expect(await noDidData.pageCount).toBe(1); + expect(noDid.fetchMock).not.toHaveBeenCalled(); + + const failing = makeEvent(); + failing.fetchMock.mockResolvedValue(new Response("boom", { status: 502 })); + const failingData = await runLoad(failing.event); + expect(await failingData.forks).toEqual([]); + expect(await failingData.pageCount).toBe(1); + }); +}); -- 2.54.0