mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-12 19:06:06 +02:00
* fix(issues): return to the source list after deleting an issue Deleting an issue from its detail page always pushed the workspace Issues list, so opening an issue from My Issues (or a project list, search, a pin, an agent panel) and deleting it dropped the user's navigation context — GH #5995. Go back instead. `useBackOrReplace` steps back when the platform reports in-app history and replaces with a fallback path when there is none, so a shared link opened cold or a new tab never steps off the app. Web answers via the Navigation API, falling back to counting its own pushes; desktop reads the active tab's virtual history. `replace`, not `push`: the deleted issue's URL must not stay in history for the back button to land on a 404. The not-found "Back to Issues" button loses the same context, so it moves to the same helper and its label becomes a plain "Back". Co-authored-by: multica-agent <github@multica.ai> * fix(web): track history position, not push count, for canGoBack The Navigation API fallback only ever counted pushes, so `pushes > 0` did not mean the current entry still had an in-app page behind it. Cold-open an issue, click Issues, press the browser's Back button, then delete: the tracker still claimed history and `back()` would step off Multica — the exact case the fallback exists to prevent. Count depth instead: a push adds one, any traversal takes one away (clamped at zero). Reaching the document's first entry requires traversing back at least as many times as we pushed, so a positive depth can never be claimed while sitting on it. A browser Forward is now conservative — it reports no history where a step back would have been fine — which costs a fallback navigation rather than an exit. Also corrects the useBackOrReplace contract comment: stepping back leaves the dead URL in forward history, so the guarantee is that it never lands on the back stack, not that it leaves history entirely. Co-authored-by: multica-agent <github@multica.ai> * fix(web): answer canGoBack from the browser alone, never from push counts Review found the counter still lied, one level deeper: it counted `router.push` calls, and a call is not a committed history entry. Next drops a push to `replaceState` when the canonical URL is unchanged (app-router.js, the `pendingPush && href !== canonicalUrl` branch), so clicking a self-link — the breadcrumb on an issue detail page, a pin to the issue you are on — incremented depth with no entry behind it, and a delete from there could still step off the app. Fixing the count needs per-entry markers, which means depending on both React effect ordering (our provider's effect runs before app-router's history commit) and Next's own preserveCustomHistoryState behaviour. Two rounds of review have now found holes in hand-rolled history tracking; a third layer of it is not the way to buy this guarantee. So stop deriving. `canGoBack` is the Navigation API's answer or `false`. Browsers without it take the fallback path, which is exactly what they did before any of this existed — nobody regresses, and the "wrong true walks the user out of the app" failure is now unreachable by construction. Drops the tracker, the popstate wiring and the push wrapper: the `multica:navigate` bridge returns to its original shape. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai>
71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
"use client";
|
|
|
|
import { Suspense, useEffect } from "react";
|
|
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
|
import {
|
|
NavigationProvider,
|
|
type NavigationAdapter,
|
|
} from "@multica/views/navigation";
|
|
import { canGoBackInApp } from "./in-app-history";
|
|
|
|
/**
|
|
* Web half of the `multica:navigate` bridge — the event shared content
|
|
* (comments, chat, issue descriptions) fires when a link resolves to an in-app
|
|
* destination. Desktop's shell answers it by opening a tab; on the web the
|
|
* equivalent is a router push in place. Without this the event has no listener
|
|
* and such links do nothing at all.
|
|
*/
|
|
function useInternalLinkHandler(router: ReturnType<typeof useRouter>) {
|
|
useEffect(() => {
|
|
const handler = (e: Event) => {
|
|
const path = (e as CustomEvent<{ path?: string }>).detail?.path;
|
|
if (!path) return;
|
|
router.push(path);
|
|
};
|
|
window.addEventListener("multica:navigate", handler);
|
|
return () => window.removeEventListener("multica:navigate", handler);
|
|
}, [router]);
|
|
}
|
|
|
|
function NavigationProviderInner({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode;
|
|
}) {
|
|
const router = useRouter();
|
|
const pathname = usePathname();
|
|
const searchParams = useSearchParams();
|
|
useInternalLinkHandler(router);
|
|
|
|
const adapter: NavigationAdapter = {
|
|
push: router.push,
|
|
replace: router.replace,
|
|
back: router.back,
|
|
canGoBack: canGoBackInApp,
|
|
pathname,
|
|
searchParams: new URLSearchParams(searchParams.toString()),
|
|
getShareableUrl: (path: string) =>
|
|
typeof window === "undefined" ? path : window.location.origin + path,
|
|
// router.prefetch is a no-op in dev mode by Next.js design; in production
|
|
// it warms the RSC payload + route chunk so the next push() commits with
|
|
// no network round-trip. Safe to call repeatedly — Next dedupes internally.
|
|
prefetch: (path: string) => {
|
|
router.prefetch(path);
|
|
},
|
|
};
|
|
|
|
return <NavigationProvider value={adapter}>{children}</NavigationProvider>;
|
|
}
|
|
|
|
export function WebNavigationProvider({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<Suspense>
|
|
<NavigationProviderInner>{children}</NavigationProviderInner>
|
|
</Suspense>
|
|
);
|
|
}
|