Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/app/dashboard/maintainer/PipelineBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,15 @@ export const ESCROW_LOCKED_EXCLUDED_STATUSES: BountyStatus[] = [
"expired",
];

// Cap per-column rendering so a busy repo's pipeline (potentially hundreds
// of bounties in one status) doesn't render an unbounded, unvirtualized
// list of cards on every dashboard visit (#226).
const MAX_VISIBLE_PER_COLUMN = 8;

function PipelineColumn({ label, bounties }: { label: string; bounties: Bounty[] }) {
const visible = bounties.slice(0, MAX_VISIBLE_PER_COLUMN);
const hiddenCount = bounties.length - visible.length;

return (
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between px-1">
Expand All @@ -51,10 +59,11 @@ function PipelineColumn({ label, bounties }: { label: string; bounties: Bounty[]
</span>
</div>
<div className="mt-3 space-y-2">
{bounties.map((b) => (
{visible.map((b) => (
<Link
key={b.id}
href={`/issues/${b.id}`}
title={b.title}
className="block rounded-xl border border-slate-200 bg-white p-3 shadow-sm transition-colors hover:border-indigo-300 dark:border-slate-800 dark:bg-slate-900 dark:hover:border-indigo-700"
>
<p className="truncate text-xs text-slate-400 dark:text-slate-500">
Expand All @@ -73,6 +82,14 @@ function PipelineColumn({ label, bounties }: { label: string; bounties: Bounty[]
Nothing here
</p>
)}
{hiddenCount > 0 && (
<Link
href="/issues"
className="block rounded-xl border border-dashed border-slate-200 px-3 py-2 text-center text-xs font-medium text-indigo-600 transition-colors hover:border-indigo-300 dark:border-slate-800 dark:text-indigo-400 dark:hover:border-indigo-700"
>
View all {bounties.length} in Bounty pipeline
</Link>
)}
</div>
</div>
);
Expand Down
10 changes: 9 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,19 @@ export default function RootLayout({
<script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
</head>
<body className="flex min-h-full flex-col bg-[#fbfbfd] text-slate-900 dark:bg-[#0a0a0f] dark:text-white">
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-4 focus:z-50 focus:rounded-lg focus:bg-indigo-600 focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-white"
>
Skip to content
</a>
<ThemeProvider>
<AuthProvider>
<WalletProvider>
<Navbar />
<main className="flex-1">{children}</main>
<main id="main-content" className="flex-1">
{children}
</main>
<Footer />
</WalletProvider>
</AuthProvider>
Expand Down
10 changes: 7 additions & 3 deletions src/context/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,13 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {

useEffect(() => {
// Session hydration on mount: reads the JWT from localStorage and
// resolves the current user. Inherently async, not a render-time value.
// eslint-disable-next-line react-hooks/set-state-in-effect
void refresh();
// resolves the current user. Deferred by a tick (#223) so it runs after
// the initial commit instead of on the critical path to interactivity —
// every route mounts this provider, including static marketing pages.
const id = window.setTimeout(() => {
void refresh();
}, 0);
return () => window.clearTimeout(id);
}, [refresh]);

const handleTokenChangedElsewhere = useCallback(
Expand Down
11 changes: 8 additions & 3 deletions src/context/WalletContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@ export function WalletProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
// localStorage is unavailable during SSR, so this can't be a lazy
// useState initializer — it must run after mount on the client.
const stored = window.localStorage.getItem(WALLET_KEY);
// eslint-disable-next-line react-hooks/set-state-in-effect
if (stored) setAddress(stored);
// Deferred by a tick (#223), same as AuthProvider's mount hydration,
// so this provider (mounted on every route) doesn't add to the
// critical path to interactivity for routes that don't need it yet.
const id = window.setTimeout(() => {
const stored = window.localStorage.getItem(WALLET_KEY);
if (stored) setAddress(stored);
}, 0);
return () => window.clearTimeout(id);
}, []);

const handleWalletKeyChangedElsewhere = useCallback((newValue: string | null) => {
Expand Down