csr tangled client in solid-js
15

Configure Feed

Select the types of activity you want to include in your feed.

reduce layout shift when loading repo

dawn (May 22, 2026, 9:08 PM +0300) 55d6ae4c f7b3f000

+255 -70
+199 -51
src/components/repo.tsx
··· 39 39 icon: JSX.Element; 40 40 label: string; 41 41 meta?: string; 42 + reserveMeta?: boolean; 42 43 }> = (props) => ( 43 44 <A href={props.href} class="relative -mr-px group no-underline hover:no-underline"> 44 45 <div ··· 50 51 <span class="flex items-center justify-center gap-2"> 51 52 {props.icon} 52 53 {props.label} 53 - <Show when={props.meta}> 54 - <span class="rounded bg-gray-200 px-1 text-sm text-gray-700 dark:bg-gray-700 dark:text-gray-200"> 55 - {props.meta} 54 + <Show when={props.meta !== undefined || props.reserveMeta}> 55 + <span 56 + aria-hidden={props.meta === undefined ? 'true' : undefined} 57 + class={clsx( 58 + 'untangled-repo-tab-meta rounded bg-gray-200 px-1 text-sm text-gray-700 dark:bg-gray-700 dark:text-gray-200', 59 + props.meta === undefined && 'untangled-repo-tab-meta-placeholder', 60 + )} 61 + > 62 + {props.meta ?? '0'} 56 63 </span> 57 64 </Show> 58 65 </span> ··· 67 74 const prDiffFileSkeletons = Array.from({ length: 2 }); 68 75 const prDiffCodeLines = Array.from({ length: 8 }); 69 76 70 - export const RepoTreeSkeleton: Component = () => ( 71 - <div class="flex flex-col gap-4"> 72 - <div class={cardStyles('overflow-hidden')}> 73 - <SkeletonBlock class="h-2 rounded-none" /> 74 - <div class="flex flex-wrap items-center gap-3 border-b border-gray-200 px-4 py-3 dark:border-gray-700"> 75 - <SkeletonBlock class="h-8 w-32" /> 76 - <SkeletonBlock class="h-6 w-48" /> 77 - <SkeletonBlock class="ml-auto h-6 w-24" /> 78 - </div> 79 - <div class="divide-y divide-gray-100 dark:divide-gray-700"> 80 - <For each={skeletonRows}> 81 - {(_, index) => ( 82 - <div class="untangled-skeleton-file-row px-4 py-3"> 83 - <div class="flex min-w-0 items-center gap-3"> 84 - <SkeletonBlock class="size-5 shrink-0 rounded-sm" /> 85 - <SkeletonBlock class={index() % 3 === 0 ? 'h-5 w-1/3' : 'h-5 w-2/3'} /> 86 - </div> 87 - <SkeletonBlock class="h-4 w-24 justify-self-end" /> 77 + export const RepoLanguageBarSkeleton: Component = () => ( 78 + <div class="group -my-4 -m-6 mb-4" aria-hidden="true"> 79 + <div class="untangled-language-bar untangled-language-bar-loading"> 80 + <SkeletonBlock class="untangled-language-bar-skeleton" /> 81 + </div> 82 + </div> 83 + ); 84 + 85 + const TreeRowsSkeleton: Component = () => ( 86 + <div class="space-y-3"> 87 + <For each={skeletonRows}> 88 + {(_, index) => ( 89 + <div class="untangled-file-row"> 90 + <div class="flex min-w-0 items-center gap-2"> 91 + <SkeletonBlock class="size-4 shrink-0 rounded-sm" /> 92 + <SkeletonBlock class={index() % 3 === 0 ? 'h-5 w-1/3' : 'h-5 w-2/3'} /> 93 + </div> 94 + <SkeletonBlock class="h-4 w-16 justify-self-end" /> 95 + </div> 96 + )} 97 + </For> 98 + </div> 99 + ); 100 + 101 + const RepoOverviewSidebarSkeleton: Component = () => ( 102 + <div class="min-w-0 md:pt-0 md:pl-6 border-gray-100 dark:border-gray-700 md:border-t-0 md:border-l"> 103 + <div class="flex items-center gap-2 pb-3 font-bold"> 104 + <SkeletonBlock class="size-4" /> 105 + <SkeletonBlock class="h-5 w-20" /> 106 + <SkeletonBlock class="h-5 w-8" /> 107 + </div> 108 + <div class="space-y-6"> 109 + <For each={Array.from({ length: 5 })}> 110 + {(_, index) => ( 111 + <div> 112 + <SkeletonBlock class={index() % 2 === 0 ? 'h-5 w-5/6' : 'h-5 w-2/3'} /> 113 + <div class="mt-2 flex flex-wrap items-center gap-2"> 114 + <SkeletonBlock class="h-6 w-20" /> 115 + <SkeletonBlock class="size-6 rounded-full" /> 116 + <SkeletonBlock class="h-4 w-24" /> 88 117 </div> 89 - )} 90 - </For> 91 - </div> 118 + </div> 119 + )} 120 + </For> 92 121 </div> 93 - <div class={cardStyles('overflow-hidden')}> 94 - <div class="border-b border-gray-200 px-4 py-3 dark:border-gray-700"> 95 - <SkeletonBlock class="h-5 w-28" /> 122 + <div class="mt-6 border-t border-gray-200 pt-4 dark:border-gray-700"> 123 + <div class="flex items-center gap-2 pb-2 font-bold"> 124 + <SkeletonBlock class="size-4" /> 125 + <SkeletonBlock class="h-5 w-20" /> 126 + <SkeletonBlock class="h-5 w-8" /> 96 127 </div> 97 - <div class="space-y-3 p-5"> 98 - <SkeletonBlock class="h-5 w-3/4" /> 99 - <SkeletonBlock class="h-5 w-11/12" /> 100 - <SkeletonBlock class="h-5 w-2/3" /> 101 - <SkeletonBlock class="h-5 w-5/6" /> 128 + <div class="space-y-2"> 129 + <SkeletonBlock class="h-4 w-2/3" /> 130 + <SkeletonBlock class="h-4 w-1/2" /> 102 131 </div> 103 132 </div> 104 133 </div> 105 134 ); 106 135 136 + export const RepoTreeSkeleton: Component<{ nested?: boolean }> = (props) => ( 137 + <> 138 + <section class="bg-white dark:bg-gray-800 px-6 py-4 rounded relative w-full mx-auto dark:text-white"> 139 + <RepoLanguageBarSkeleton /> 140 + <div class="flex items-center justify-between gap-4 pb-5"> 141 + <div class="flex items-center gap-2"> 142 + <SkeletonBlock class="h-8 w-36" /> 143 + <SkeletonBlock class="size-8" /> 144 + </div> 145 + <SkeletonBlock class="h-8 w-40" /> 146 + </div> 147 + 148 + <Show when={props.nested}> 149 + <div> 150 + <div class="pb-2 mb-3 border-b border-gray-200 dark:border-gray-700"> 151 + <SkeletonBlock class="h-5 w-2/3" /> 152 + </div> 153 + <div class="pb-2 mb-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between flex-wrap gap-2"> 154 + <div class="flex min-w-0 flex-wrap items-center gap-2"> 155 + <SkeletonBlock class="size-6 rounded-full" /> 156 + <SkeletonBlock class="h-4 w-28" /> 157 + <SkeletonBlock class="h-4 w-2/5" /> 158 + <SkeletonBlock class="h-4 w-20" /> 159 + </div> 160 + <SkeletonBlock class="h-6 w-20" /> 161 + </div> 162 + </div> 163 + </Show> 164 + 165 + <div class={clsx('untangled-repo-overview', props.nested && 'untangled-repo-overview--path')}> 166 + <div class="untangled-file-tree-pane min-w-0 pr-0 md:pr-6"> 167 + <TreeRowsSkeleton /> 168 + </div> 169 + <Show when={!props.nested}> 170 + <RepoOverviewSidebarSkeleton /> 171 + </Show> 172 + </div> 173 + </section> 174 + <div class="mt-4 rounded bg-white dark:bg-gray-800 shadow-sm w-full mx-auto overflow-hidden"> 175 + <div class="px-4 py-2 border-b border-gray-200 dark:border-gray-600 flex items-center gap-2"> 176 + <SkeletonBlock class="size-4" /> 177 + <SkeletonBlock class="h-4 w-24" /> 178 + </div> 179 + <section class="px-6 py-6 overflow-auto"> 180 + <div class="space-y-3"> 181 + <SkeletonBlock class="h-5 w-3/4" /> 182 + <SkeletonBlock class="h-5 w-11/12" /> 183 + <SkeletonBlock class="h-5 w-2/3" /> 184 + <SkeletonBlock class="h-5 w-5/6" /> 185 + </div> 186 + </section> 187 + </div> 188 + </> 189 + ); 190 + 107 191 export const RepoBlobSkeleton: Component = () => ( 108 192 <div class={cardStyles('overflow-hidden')}> 109 193 <div class="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 px-4 py-3 dark:border-gray-700"> ··· 153 237 </div> 154 238 )} 155 239 </For> 240 + </div> 241 + ); 242 + 243 + export const RepoListPageSkeleton: Component<{ 244 + kind: 'issues' | 'pulls'; 245 + showCreate?: boolean; 246 + }> = (props) => ( 247 + <> 248 + <div class="rounded bg-white p-4 dark:bg-gray-800"> 249 + <div class="untangled-filter-grid"> 250 + <div class="flex items-stretch rounded border border-gray-300 dark:border-gray-700"> 251 + <SkeletonBlock class="h-8 w-20 rounded-none" /> 252 + <SkeletonBlock class="h-8 w-20 rounded-none" /> 253 + <Show when={props.kind === 'pulls'}> 254 + <SkeletonBlock class="h-8 w-24 rounded-none" /> 255 + </Show> 256 + </div> 257 + <div class="untangled-repo-filter-search"> 258 + <SkeletonBlock class="h-8 w-full" /> 259 + </div> 260 + <Show when={props.showCreate}> 261 + <SkeletonBlock class="h-8 w-28 justify-self-end" /> 262 + </Show> 263 + </div> 264 + </div> 265 + <div class="mt-2"> 266 + <RepoListSkeleton kind={props.kind} /> 267 + </div> 268 + </> 269 + ); 270 + 271 + export const RepoFormSkeleton: Component<{ titleWidth?: string }> = (props) => ( 272 + <div class={cardStyles('p-6')}> 273 + <div class="flex flex-col gap-4"> 274 + <SkeletonBlock class={clsx('h-8', props.titleWidth ?? 'w-40')} /> 275 + <SkeletonBlock class="h-10 w-full" /> 276 + <SkeletonBlock class="h-64 w-full" /> 277 + <SkeletonBlock class="h-9 w-32 self-end" /> 278 + </div> 279 + </div> 280 + ); 281 + 282 + export const PullComposeSkeleton: Component = () => ( 283 + <div id="pr-compose-host" class="flex flex-col bg-white dark:bg-gray-800 px-6 py-4 rounded relative w-full mx-auto dark:text-white"> 284 + <div class="flex flex-col gap-6"> 285 + <For each={Array.from({ length: 3 })}> 286 + {(_, index) => ( 287 + <section class="untangled-new-pr-step"> 288 + <div class="untangled-new-pr-heading"> 289 + <SkeletonBlock class="size-6 rounded-full" /> 290 + <SkeletonBlock class={index() === 0 ? 'h-4 w-16' : 'h-4 w-20'} /> 291 + </div> 292 + <div class="ml-10 flex flex-col gap-3"> 293 + <SkeletonBlock class="h-14 w-full" /> 294 + <SkeletonBlock class={index() === 1 ? 'h-56 w-full' : 'h-10 w-2/3'} /> 295 + </div> 296 + </section> 297 + )} 298 + </For> 299 + </div> 156 300 </div> 157 301 ); 158 302 ··· 382 526 </Show> 383 527 ); 384 528 385 - export const RepoFrameSkeleton: Component<{ active: 'code' | 'issues' | 'pulls' }> = (props) => ( 529 + const defaultRepoFrameSkeletonContent = (active: 'code' | 'issues' | 'pulls') => 530 + active === 'code' ? <RepoTreeSkeleton /> : <RepoListPageSkeleton kind={active === 'issues' ? 'issues' : 'pulls'} />; 531 + 532 + export const RepoFrameSkeleton: Component<{ 533 + active: 'code' | 'issues' | 'pulls'; 534 + children?: JSX.Element; 535 + }> = (props) => ( 386 536 <> 387 - <section class="flex w-full flex-col gap-4 px-4 py-5"> 388 - <div class="flex flex-wrap items-start justify-between gap-4"> 389 - <div class="min-w-0 flex-1"> 390 - <div class="flex min-w-0 items-center gap-2"> 537 + <section id="repo-header" class="mb-2 py-2 px-4"> 538 + <div class="flex flex-col sm:flex-row items-start gap-4 justify-between mb-2"> 539 + <div class="flex gap-2"> 540 + <div class="flex items-center gap-2 flex-wrap text-lg"> 391 541 <SkeletonBlock class="size-8 rounded-full" /> 392 542 <SkeletonBlock class="h-6 w-40" /> 393 543 </div> 394 - <div class="mt-3 flex flex-wrap items-center gap-2"> 395 - <SkeletonBlock class="h-5 w-72" /> 396 - <SkeletonBlock class="h-5 w-24" /> 397 - </div> 398 544 </div> 399 - <div class="flex items-center gap-2"> 400 - <SkeletonBlock class="h-9 w-24" /> 401 - <SkeletonBlock class="h-9 w-20" /> 545 + <div class="hidden sm:block sm:flex-shrink-0"> 546 + <div class="untangled-repo-actions"> 547 + <SkeletonBlock class="h-8 w-32" /> 548 + <SkeletonBlock class="h-8 w-20" /> 549 + </div> 402 550 </div> 551 + </div> 552 + <div class="flex flex-wrap items-center gap-x-4 gap-y-2 text-sm"> 553 + <SkeletonBlock class="h-5 w-72" /> 554 + <SkeletonBlock class="h-5 w-24" /> 403 555 </div> 404 556 </section> 405 557 <section class="w-full flex flex-col"> 406 558 <nav class="w-full pl-4 overflow-auto"> 407 559 <div class="flex z-60"> 408 560 <RepoTabLink active={props.active === 'code'} href="#" icon={<SkeletonBlock class="size-4" />} label="overview" /> 409 - <RepoTabLink active={props.active === 'issues'} href="#" icon={<SkeletonBlock class="size-4" />} label="issues" /> 410 - <RepoTabLink active={props.active === 'pulls'} href="#" icon={<SkeletonBlock class="size-4" />} label="pulls" /> 561 + <RepoTabLink active={props.active === 'issues'} href="#" icon={<SkeletonBlock class="size-4" />} label="issues" reserveMeta /> 562 + <RepoTabLink active={props.active === 'pulls'} href="#" icon={<SkeletonBlock class="size-4" />} label="pulls" reserveMeta /> 411 563 </div> 412 564 </nav> 413 - <div class="mt-2 px-4 pb-6"> 414 - <Show when={props.active === 'code'} fallback={<RepoListSkeleton kind={props.active === 'issues' ? 'issues' : 'pulls'} />}> 415 - <RepoTreeSkeleton /> 416 - </Show> 417 - </div> 565 + {props.children ?? defaultRepoFrameSkeletonContent(props.active)} 418 566 </section> 419 567 </> 420 568 );
+24
src/index.css
··· 861 861 list-style: none; 862 862 } 863 863 864 + .untangled-language-bar-loading { 865 + cursor: default; 866 + pointer-events: none; 867 + } 868 + 869 + .untangled-language-bar-skeleton { 870 + width: 100%; 871 + height: 100%; 872 + border-radius: 0.25rem 0.25rem 0 0; 873 + } 874 + 864 875 .untangled-language-bar::-webkit-details-marker { 865 876 display: none; 866 877 } ··· 1399 1410 1400 1411 .untangled-repo-action-main { 1401 1412 gap: 0.5rem; 1413 + min-width: 4.75rem; 1402 1414 padding: 0 0.75rem; 1403 1415 flex: 1 1 auto; 1404 1416 justify-content: center; ··· 1426 1438 .untangled-repo-action-count { 1427 1439 border-left: 1px solid rgb(209 213 219); 1428 1440 background: rgb(249 250 251); 1441 + justify-content: center; 1442 + min-width: 2.5rem; 1429 1443 padding: 0 0.75rem; 1430 1444 position: relative; 1431 1445 text-decoration: none; ··· 1438 1452 1439 1453 .untangled-repo-tab { 1440 1454 min-width: 80px; 1455 + } 1456 + 1457 + .untangled-repo-tab-meta { 1458 + display: inline-flex; 1459 + justify-content: center; 1460 + min-width: 1.25rem; 1461 + } 1462 + 1463 + .untangled-repo-tab-meta-placeholder { 1464 + visibility: hidden; 1441 1465 } 1442 1466 1443 1467 .group:hover .untangled-repo-tab-idle {
+10 -8
src/pages/repo/code.tsx
··· 5 5 import { For, Match, Show, Switch, createEffect, createMemo, createSignal, type Component } from 'solid-js'; 6 6 import { buildBlobDataUrl, decodeBlobText, getRepoBlob, getRepoBranches, getRepoDefaultBranch, getRepoLanguages, getRepoLog, getRepoTags, getRepoTree } from '../../lib/api/repos'; 7 7 import { Avatar, ErrorState, PlaceholderAvatar, buttonStyles, cardStyles } from '../../components/common'; 8 - import { CloneDropdown, CodeView, FileRow, MarkdownBlock, OverviewFileRow, ReadmeCard, RepoTreeSkeleton } from '../../components/repo'; 8 + import { CloneDropdown, CodeView, FileRow, MarkdownBlock, OverviewFileRow, ReadmeCard, RepoBlobSkeleton, RepoLanguageBarSkeleton, RepoTreeSkeleton } from '../../components/repo'; 9 9 import { RepoFrame, useRepoQuery } from './shared'; 10 10 import { blobHref, commitHref, commitsHref, countLines, decodeRoutePath, formatBytes, formatLanguagePercent, formatRelativeTime, getErrorMessage, getParentPath, imageLike, isDirectory, joinPath, languageColor, markdownLike, safeDecode, sortedTreeEntries, svgLike, treeHref, videoLike } from '../../lib/repo-utils'; 11 11 import { LOADING_DELAY_MS, hasNamedRef, normalizeLanguages, resolveDefaultBranchName, resolveRouteRefAndPath, shouldAnimateNavigation, useDelayedLoading } from './code-helpers'; ··· 117 117 118 118 const RepoLanguageBar: Component<{ 119 119 languages: Array<{ name: string; percentage: number; color?: string }>; 120 + loading?: boolean; 120 121 }> = (props) => ( 121 - <Show when={props.languages.length > 0}> 122 + <Show when={props.languages.length > 0} fallback={<Show when={props.loading}><RepoLanguageBarSkeleton /></Show>}> 122 123 <details class="group -my-4 -m-6 mb-4"> 123 124 <summary class="untangled-language-bar"> 124 125 <For each={props.languages}> ··· 228 229 }); 229 230 230 231 return ( 231 - <RepoFrame active="code"> 232 + <RepoFrame active="code" loadingFallback={<RepoTreeSkeleton nested={Boolean(path())} />}> 232 233 <Switch> 233 234 <Match when={codeQuery.isLoading}> 234 - <RepoTreeSkeleton /> 235 + <RepoTreeSkeleton nested={Boolean(path())} /> 235 236 </Match> 236 237 <Match when={codeQuery.error}> 237 238 <div class={cardStyles('p-6')}> ··· 513 514 const overviewReady = createMemo(() => 514 515 Boolean(repoQuery.data && treeQuery.data && branchesQuery.data && tagsQuery.data && logQuery.data), 515 516 ); 517 + const languagesLoading = createMemo(() => !languagesQuery.data && (languagesQuery.isLoading || languagesQuery.isFetching)); 516 518 const showOverviewSkeleton = useDelayedLoading(() => !overviewReady() && !overviewError()); 517 519 518 520 createEffect(() => { ··· 553 555 }; 554 556 555 557 return ( 556 - <RepoFrame active="code"> 558 + <RepoFrame active="code" loadingFallback={<RepoTreeSkeleton nested={Boolean(routePath())} />}> 557 559 <Switch> 558 560 <Match when={!overviewReady() && !overviewError() && showOverviewSkeleton()}> 559 - <RepoTreeSkeleton /> 561 + <RepoTreeSkeleton nested={Boolean(path())} /> 560 562 </Match> 561 563 <Match when={overviewError()}> 562 564 <div class={cardStyles('p-6')}> ··· 584 586 return ( 585 587 <> 586 588 <section class="bg-white dark:bg-gray-800 px-6 py-4 rounded relative w-full mx-auto dark:text-white"> 587 - <RepoLanguageBar languages={languages} /> 589 + <RepoLanguageBar languages={languages} loading={languagesLoading()} /> 588 590 589 591 <div class="flex items-center justify-between gap-4 pb-5"> 590 592 <div class="flex items-center gap-2"> ··· 860 862 }); 861 863 862 864 return ( 863 - <RepoFrame active="code"> 865 + <RepoFrame active="code" loadingFallback={<RepoBlobSkeleton />}> 864 866 <Switch> 865 867 <Match when={blobQuery.isLoading && repoQuery.data}> 866 868 <LoadingFilePath repo={repoQuery.data!} refName={ref()} path={path()} />
+10 -2
src/pages/repo/commits.tsx
··· 503 503 }); 504 504 505 505 return ( 506 - <RepoFrame active="code"> 506 + <RepoFrame 507 + active="code" 508 + loadingFallback={ 509 + <section id="commit-table" class="bg-white dark:bg-gray-800 px-6 py-4 rounded relative w-full mx-auto dark:text-white overflow-x-auto"> 510 + <h2 class="font-bold text-sm mb-4 uppercase dark:text-white">commits</h2> 511 + <CommitListSkeleton /> 512 + </section> 513 + } 514 + > 507 515 <section id="commit-table" class="bg-white dark:bg-gray-800 px-6 py-4 rounded relative w-full mx-auto dark:text-white overflow-x-auto"> 508 516 <h2 class="font-bold text-sm mb-4 uppercase dark:text-white">commits</h2> 509 517 <Switch> ··· 715 723 const patch = createMemo(() => renderUnifiedDiff(commitQuery.data?.diff)); 716 724 717 725 return ( 718 - <RepoFrame active="code" fullWidth> 726 + <RepoFrame active="code" fullWidth loadingFallback={<CommitDetailSkeleton />}> 719 727 <Switch> 720 728 <Match when={commitQuery.isLoading}> 721 729 <CommitDetailSkeleton />
+4 -4
src/pages/repo/issues.tsx
··· 10 10 import type { RepoContext } from '../../lib/api/repos'; 11 11 import { useAuth } from '../../lib/auth'; 12 12 import { Avatar, ErrorState, PaginationControls, StateBadge, ToggleButton, buttonStyles, cardStyles, inputStyles, textareaStyles } from '../../components/common'; 13 - import { AtUriPanel, MarkdownBlock, RepoListSkeleton, RepoThreadSkeleton } from '../../components/repo'; 13 + import { AtUriPanel, MarkdownBlock, RepoFormSkeleton, RepoListPageSkeleton, RepoListSkeleton, RepoThreadSkeleton } from '../../components/repo'; 14 14 import { RepoFrame, issueQueryKey, issuesQueryKey, useRepoQuery } from './shared'; 15 15 import { REPO_LIST_PAGE_LIMIT, formatRelativeTime, getErrorMessage, issueHref, parseIntegerSearchParam, uniqueCommenters } from '../../lib/repo-utils'; 16 16 import { buildIssueCommentThreads, buildIssueFilterQuery, parseIssueFilter } from './issues-helpers'; ··· 83 83 const paginated = createMemo(() => issuesQuery.data?.items ?? []); 84 84 85 85 return ( 86 - <RepoFrame active="issues"> 86 + <RepoFrame active="issues" loadingFallback={<RepoListPageSkeleton kind="issues" showCreate={Boolean(auth.agent())} />}> 87 87 <div class="rounded bg-white p-4 dark:bg-gray-800"> 88 88 <div class="untangled-filter-grid"> 89 89 <div class="flex items-stretch rounded border border-gray-300 dark:border-gray-700"> ··· 239 239 }; 240 240 241 241 return ( 242 - <RepoFrame active="issues"> 242 + <RepoFrame active="issues" loadingFallback={<RepoFormSkeleton titleWidth="w-36" />}> 243 243 <div class={cardStyles('p-6')}> 244 244 <form onSubmit={submit} class="flex flex-col gap-4"> 245 245 <h1 class="text-2xl font-bold m-0">new issue</h1> ··· 422 422 }; 423 423 424 424 return ( 425 - <RepoFrame active="issues"> 425 + <RepoFrame active="issues" loadingFallback={<RepoThreadSkeleton kind="issue" />}> 426 426 <Switch> 427 427 <Match when={issueQuery.isLoading}> 428 428 <RepoThreadSkeleton kind="issue" />
+4 -4
src/pages/repo/pulls.tsx
··· 24 24 import { compareBranches, getRepoBranches, type RepoContext } from '../../lib/api/repos'; 25 25 import { useAuth } from '../../lib/auth'; 26 26 import { Avatar, ErrorState, LoadingState, PaginationControls, StateBadge, ToggleButton, buttonStyles, cardStyles, textareaStyles } from '../../components/common'; 27 - import { AtUriPanel, BranchPill, CommentCard, DiffView, MarkdownBlock, PullDiffSkeleton, PullDiffView, RepoListSkeleton, RepoThreadSkeleton } from '../../components/repo'; 27 + import { AtUriPanel, BranchPill, CommentCard, DiffView, MarkdownBlock, PullComposeSkeleton, PullDiffSkeleton, PullDiffView, RepoListPageSkeleton, RepoListSkeleton, RepoThreadSkeleton } from '../../components/repo'; 28 28 import { RepoFrame, pullQueryKey, pullsQueryKey, useRepoQuery } from './shared'; 29 29 import { REPO_LIST_PAGE_LIMIT, formatRelativeTime, getErrorMessage, parseIntegerSearchParam, pullHref, uniqueCommenters } from '../../lib/repo-utils'; 30 30 import { buildPullFilterQuery, parsePastedPatchPrefill, parsePullFilter, type PullSourceMode } from './pulls-helpers'; ··· 97 97 const paginated = createMemo(() => pullsQuery.data?.items ?? []); 98 98 99 99 return ( 100 - <RepoFrame active="pulls"> 100 + <RepoFrame active="pulls" loadingFallback={<RepoListPageSkeleton kind="pulls" showCreate={Boolean(auth.agent())} />}> 101 101 <div class="rounded bg-white p-4 dark:bg-gray-800"> 102 102 <div class="untangled-filter-grid"> 103 103 <div class="flex items-stretch rounded border border-gray-300 dark:border-gray-700"> ··· 359 359 }; 360 360 361 361 return ( 362 - <RepoFrame active="pulls"> 362 + <RepoFrame active="pulls" loadingFallback={<PullComposeSkeleton />}> 363 363 <div id="pr-compose-host" class="flex flex-col bg-white dark:bg-gray-800 px-6 py-4 rounded relative w-full mx-auto dark:text-white"> 364 364 <form onSubmit={submit} class="flex flex-col gap-6"> 365 365 <section class="untangled-new-pr-step"> ··· 973 973 }); 974 974 975 975 return ( 976 - <RepoFrame active="pulls"> 976 + <RepoFrame active="pulls" loadingFallback={<RepoThreadSkeleton kind="pull" />}> 977 977 <Switch> 978 978 <Match when={pullQuery.isLoading}> 979 979 <RepoThreadSkeleton kind="pull" />
+4 -1
src/pages/repo/shared.tsx
··· 55 55 active: 'code' | 'issues' | 'pulls'; 56 56 children: JSX.Element; 57 57 fullWidth?: boolean; 58 + loadingFallback?: JSX.Element; 58 59 }> = (props) => { 59 60 const auth = useAuth(); 60 61 const live = useLiveEvents(); ··· 197 198 <Switch> 198 199 <Match when={repoQuery.isLoading}> 199 200 <div class={props.fullWidth ? 'untangled-repo-frame-full' : undefined}> 200 - <RepoFrameSkeleton active={props.active} /> 201 + <RepoFrameSkeleton active={props.active}>{props.loadingFallback}</RepoFrameSkeleton> 201 202 </div> 202 203 </Match> 203 204 <Match when={repoQuery.error}> ··· 358 359 icon={<CircleDot class="size-4" />} 359 360 label="issues" 360 361 meta={issueCountQuery.data === undefined ? undefined : String(issueCountQuery.data)} 362 + reserveMeta 361 363 /> 362 364 <RepoTabLink 363 365 active={props.active === 'pulls'} ··· 365 367 icon={<GitPullRequest class="size-4" />} 366 368 label="pulls" 367 369 meta={pullCountQuery.data === undefined ? undefined : String(pullCountQuery.data)} 370 + reserveMeta 368 371 /> 369 372 </div> 370 373 </nav>