mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-11 16:36:32 +02:00
Internal navigation on web feels laggy because clicking a sidebar link blocks 0.2–0.6s with zero visual feedback — no prefetch, no Suspense fallback in the dashboard segment, and no React transition to mark the route commit as pending. This change adds the three pieces App Router needs to make the click→commit window feel instant, scoped to the (dashboard) segment so auth/landing keep their existing chrome: - NavigationAdapter gains an optional prefetch(path). The web adapter wires it to router.prefetch; desktop leaves it undefined (react-router has no equivalent and doesn't need one). AppLink prefetches on hover/focus and preserves caller-supplied onMouseEnter/onFocus/onClick. - NavigationProvider wraps push/replace in useTransition and exposes the pending flag via useIsNavigating(). Every useNavigation().push caller — sidebar AppLink, command palette, post-create modal jumps — picks this up automatically. - New apps/web/app/[workspaceSlug]/(dashboard)/loading.tsx renders a minimal skeleton during cold transitions inside the dashboard segment only. - DashboardLayout renders a 1px top progress bar driven by useIsNavigating. packages/views remains free of next/* imports; desktop is unaffected by construction (no prefetch, transition flips quickly, no loading.tsx). Co-authored-by: multica-agent <github@multica.ai>
50 lines
1.6 KiB
TypeScript
50 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import { createContext, useContext, useMemo, useTransition } from "react";
|
|
import type { NavigationAdapter } from "./types";
|
|
|
|
const NavigationContext = createContext<NavigationAdapter | null>(null);
|
|
const NavigationPendingContext = createContext<boolean>(false);
|
|
|
|
export function NavigationProvider({
|
|
value,
|
|
children,
|
|
}: {
|
|
value: NavigationAdapter;
|
|
children: React.ReactNode;
|
|
}) {
|
|
// Wrap push/replace in startTransition so any caller of useNavigation()
|
|
// (sidebar AppLink, command palette, modal post-create jumps) gets a
|
|
// React pending signal during route commit. On web this stays true until
|
|
// Next.js commits the new RSC payload; on desktop it flips off quickly
|
|
// because react-router commits synchronously — both are correct.
|
|
const [isPending, startTransition] = useTransition();
|
|
const wrapped = useMemo<NavigationAdapter>(
|
|
() => ({
|
|
...value,
|
|
push: (path: string) => startTransition(() => value.push(path)),
|
|
replace: (path: string) => startTransition(() => value.replace(path)),
|
|
}),
|
|
[value],
|
|
);
|
|
return (
|
|
<NavigationContext.Provider value={wrapped}>
|
|
<NavigationPendingContext.Provider value={isPending}>
|
|
{children}
|
|
</NavigationPendingContext.Provider>
|
|
</NavigationContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useNavigation(): NavigationAdapter {
|
|
const ctx = useContext(NavigationContext);
|
|
if (!ctx)
|
|
throw new Error("useNavigation must be used within NavigationProvider");
|
|
return ctx;
|
|
}
|
|
|
|
/** True while a transition-wrapped push/replace is committing. */
|
|
export function useIsNavigating(): boolean {
|
|
return useContext(NavigationPendingContext);
|
|
}
|