feat(routing): add App Router skeleton for splash/chat/auth/sidebar + proxy.ts

Wire the Flutter route map (lib/router/app_router.dart) to Next.js 16 App
Router. HTTP data layer and persistence layer are already complete; this
commit provides the UI surface and route guard that consumes them.

Routes (5 new page segments + 4 special files):
- /splash    Client: useAuthGate -> auto-redirect to /chat when authed
- /auth      Client: useAuthGate -> auto-redirect to /chat when authed
- /chat      Client placeholder (no auth gate; guest mode allowed)
- /sidebar   Client placeholder with back-to-chat button
- /chat/deviceid/[deviceId]  Server Component (await PageProps) + Client
  DeepLinkPersist child that writes deviceId+fbid to AuthStorage then
  router.replace('/chat')

Special files (root segment):
- loading.tsx    Server: centered spinner fallback
- error.tsx      Client boundary using v16 `unstable_retry` prop
- not-found.tsx  Server: 404 with link back to /splash
- layout.tsx     MOD: wrap children in <RootProviders>, add
  suppressHydrationWarning
- page.tsx       MOD: redirect('/splash') from server

Shared infrastructure:
- src/lib/routes.ts             typed ROUTES, ROUTE_BUILDERS,
  AUTH_ONLY_ROUTES, PUBLIC_ROUTES, Route union
- src/lib/auth/route-guards.ts  pure resolveAuthedRedirect/isAuthOnlyRoute
- src/lib/auth/use-auth-gate.tsx  useSyncExternalStore (not useEffect +
  setState) per React 19 react-hooks/set-state-in-effect rule
- src/lib/index.ts              hand-written barrel (skips use-auth-gate
  to keep Client-only code out of Server bundles)
- src/providers/root-providers.tsx  Client wrapper + <div id="toast-portal" />
- src/proxy.ts                  v16 proxy (NOT deprecated middleware.ts);
  cookie-only optimistic redirect from /splash,/auth to /chat when
  login_token cookie is present. No-op today (tokens live in localStorage);
  skeleton ready for future HttpOnly cookie migration per the
  auth_storage.ts TODO(security).

Conventions:
- AGENTS.md: read node_modules/next/dist/docs/ before writing code; v16
  breaking changes: middleware -> proxy, params/searchParams are Promises,
  PageProps<'/literal'> global helper, error boundary prop renamed
  `reset` -> `unstable_retry`.
- All placeholder UIs use the design tokens from src/app/globals.css
  (no new hex colors).
- No new runtime dependencies; no test framework added.
- src/lib/ NOT added to barrelsby.json (avoids pulling Client-only
  use-auth-gate into Server bundles).

Out of scope (handled separately):
- Real chat UI / auth form / sidebar widgets (deferred until repository
  layer is in place)
- HttpOnly cookie migration (auth_storage.ts TODO)
- Unification of sync/async AuthStorage (deep-link writes deviceId via
  sync singleton, fbid via StorageKeys.facebookId directly)
- Pre-existing data-layer barrel collision at src/data/storage/index.ts:10
  (acknowledged in replicated-mapping-creek.md)

Verified:
- pnpm next typegen: OK
- pnpm exec tsc --noEmit on new files: 0 errors
- pnpm lint: 0 errors, 0 warnings
- pnpm exec next build: blocked by pre-existing src/data/storage/index.ts
  barrel ambiguity (unrelated, see replicated-mapping-creek.md)
This commit is contained in:
2026-06-09 10:16:39 +08:00
parent a240f5965d
commit c767322db6
17 changed files with 750 additions and 63 deletions
+15 -62
View File
@@ -1,65 +1,18 @@
import Image from "next/image";
/**
* 根路径处理
*
* Server Component:把访客直接送入 `/splash`,由 splash 页(Client)根据
* `authStorage.hasLoginToken()` 决定是否再跳 `/chat`。
*
* 这里**不**做鉴权重定向:Server 端读不到 localStorage,且当前没有 HttpOnly
* cookie(见 `src/data/storage/auth/auth_storage.ts` 的 TODO)。
* `src/proxy.ts` 已为未来 cookie 化预留了基于 `login_token` cookie 的 short-circuit。
*/
import { redirect } from "next/navigation";
import { ROUTES } from "@/lib/routes";
export default function Home() {
return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the page.tsx file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={16}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
</main>
</div>
);
redirect(ROUTES.splash);
}