Files
Jyotisha/frontend/src/components/app-link.tsx
T
Jesse_ChenandClaude Opus 5.5 638b6a60c0 fix(home): warm return to / without replaying the loading ring (BUG-1040)
Coming back to / from /chart, /ephemeris, /reports or /people by client
navigation remounted Home from hydrated=false and replayed the whole
bootstrap: model catalog, consult status, entry summary and daily card,
3-5 round trips plus up to 4 s of reveal budget, every time.

The reveal now counts per document load, not per mount:
- lib/home-warm-snapshot.ts: memory-only, per-account snapshot of the
  model catalog, rectification entry summary, session cursor and today's
  card (keyed by person + birth fingerprint + day). Account, profile and
  the session list already survive in SessionListProvider. Cleared on
  account change, sign-out, provider 401 and redirectToLogin.
- lib/home-warm-start.ts: Home's useState initializers take one warm
  decision per mount. Complete snapshot + settled list + complete profile
  + a landing computable in memory -> start ready, landing resolved with
  the cold-path functions (?new=1, ?c=, login-return stash and its person
  scope from BUG-1038). Anything missing or needing a lookup -> the
  unchanged cold path (no half-reveal, BUG-1021).
- runHomeWarmRefresh commits the landing, then refreshes catalog, account
  and background-consultation recovery in the background (recovery shares
  resolveReservedConsultation with the cold path). Summary and daily card
  refresh through their existing effects.
- Next renders the new page before it writes the address bar, so AppLink /
  navigateAppPath note the target href (lib/client-navigation-target.ts);
  unknown target -> cold path.

Home() useState 33 / useRef 37 unchanged; page.tsx 1329 -> 1373 lines.

Tests: home-warm-return-lifecycle (13, real Home + sidebar + provider,
navigating in Next's render-then-write-URL order; 11 fail with warm start
disabled) and home-warm-snapshot (12). Full suite 3953 / 61 failing,
failure names identical to the 3928 / 61 baseline. Build: / stays Static,
rootMainFiles gzip 130933 B unchanged. Local Chrome with every /api/*
held 1.5 s: /chart -> 新建对话 interactive in 18-23 ms with no loading
ring (baseline 5530 ms with ring).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-09-26 08:21:35 +08:00

41 lines
1.3 KiB
TypeScript

"use client";
import Link from "next/link";
import { forwardRef, type ComponentProps } from "react";
import { hrefFromLinkTarget, interceptAppNavigation } from "@/lib/app-navigation";
import { noteClientNavigationTarget } from "@/lib/client-navigation-target";
export type AppLinkProps = ComponentProps<typeof Link>;
/**
* In-app link that stays on the client router when this tab's build matches
* the running image, and does a document load when it does not.
*
* Next 16.3.1's own mismatch fallback did not fire on this host after a
* deploy (see BUG-967); wrapping the destinations that actually no-op'd is
* the self-heal.
*/
export const AppLink = forwardRef<HTMLAnchorElement, AppLinkProps>(function AppLink(
{ onClick, href, ...props },
ref,
) {
return (
<Link
ref={ref}
href={href}
onClick={(event) => {
onClick?.(event);
const target = hrefFromLinkTarget(href);
interceptAppNavigation(event, target);
// A plain click the client router will take: remember where it goes,
// for a page that reads its URL before the router writes it (BUG-1040).
if (!event.defaultPrevented && event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) {
noteClientNavigationTarget(target);
}
}}
{...props}
/>
);
});