Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
de3a6c4
feat: adopt Next.js 16.3 preview + partialPrefetching (instant naviga…
arzafran Jun 26, 2026
b5be0d7
test(next): add instant-nav demo routes and 16.3 turbopack build flags
arzafran Jun 30, 2026
3ed2755
Merge remote-tracking branch 'origin/main' into next/16.3-preview
arzafran Jul 1, 2026
a675dfc
Merge remote-tracking branch 'origin/main' into next/16.3-preview
arzafran Jul 9, 2026
eba6df6
chore: refresh next@preview to 16.3.0-preview.6 and merge main
arzafran Jul 20, 2026
8ba6b66
merge main (starter hardening + polish + deps)
arzafran Jul 20, 2026
b26acc9
merge main into 16.3 preview
arzafran Jul 22, 2026
601e92c
fix(form): use SubmitEvent instead of the deprecated FormEvent
arzafran Jul 22, 2026
c9bf700
chore: track next@preview.8
arzafran Jul 22, 2026
6be6934
fix(demo): use min-h-dvh in the instant-nav demo routes
arzafran Jul 22, 2026
3b5f66a
docs(demo): spell out the dynamic route example paths in full
arzafran Jul 22, 2026
63fa53d
fix(demo): announce the streamed panel's loading state, drop stray co…
arzafran Jul 22, 2026
ac7de99
fix: make the prepare script safe in linked worktrees (#304)
arzafran Jul 22, 2026
710af1a
Merge remote-tracking branch 'origin/main' into next/16.3-preview
arzafran Jul 23, 2026
3402c6f
chore: track next@preview.9
arzafran Jul 23, 2026
1b2d42c
Merge remote-tracking branch 'origin/main' into next/16.3-preview
arzafran Jul 23, 2026
170683a
Merge main into next/16.3-preview and bump to 16.3.0-preview.10
arzafran Jul 29, 2026
59e24f1
perf(hooks): let the React Compiler optimize useDeviceDetection again
arzafran Jul 29, 2026
d68fa40
perf: cache Intl formatters, skip the barrels, stop walking lists twice
arzafran Jul 29, 2026
5b259fd
perf(next): turn on the rest of the 16.3 instant-navigation cluster
arzafran Jul 29, 2026
a23dcd1
perf(webgl): hold sim instances in refs and drop the effect-event han…
arzafran Jul 29, 2026
db66af7
perf(webgl,theatre): clear the last two React Compiler errors
arzafran Jul 29, 2026
fe70b46
chore(react-doctor): stop the dead-code pass reporting a template as …
arzafran Jul 29, 2026
d557a2f
a11y: give every form control a name, and use <output> for loading re…
arzafran Jul 29, 2026
381957c
perf(demo): resolve params and connection() together
arzafran Jul 29, 2026
d43181f
chore(react-doctor): scope the Theatre and theme findings by rule, no…
arzafran Jul 29, 2026
315adf1
fix(form): fire onSuccess/onError from the action, not from an effect
arzafran Jul 29, 2026
2939d79
feat(scripts): add a client-side navigation benchmark
arzafran Jul 29, 2026
ef8cd4e
feat(scripts): add a re-render cost benchmark, and correct the record
arzafran Jul 29, 2026
3ce30ad
feat(scripts): add a 404-to-home nav scenario so bench:nav runs on main
arzafran Jul 29, 2026
7474b74
Merge main into next/16.3-preview after the cherry-pick
arzafran Jul 29, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ latest tag; security fixes land on the latest release (see `SECURITY.md`).

- TypeScript 7: the typecheck gate runs on the stable native (Go) compiler — `typescript@^7.0.2` installed under the `typescript7` npm alias, invoked by path (`bun node_modules/typescript7/bin/tsc --noEmit`) from `check`, `typecheck`, `typecheck:watch`, and the lefthook pre-commit hook. The classic `typescript@^6` package keeps the `typescript` name because Next 16.2's build-time type checking and dev server require the classic JS compiler API, which the TS7 native package does not ship — when Next supports TS7 directly, the alias flips to owning the name. The preview-era machinery is gone: the `@typescript/native-preview` (`tsgo`) dependency, the `typecheck:tsc`/`typecheck:compare` cross-check scripts, and the obsolete `typescript.experimental.useTsgo` VS Code flag. Operational wins from the native compiler: the pre-commit typecheck drops `--incremental false` (the TS7 alias is the sole `.tsbuildinfo` writer, so the ~4x-faster warm cache is safe) and `typecheck:watch` uses TS7's rebuilt native file watcher.
- Whole-codebase maintainability pass (net −1.2k lines): removed a dead Zustand WebGL-tunnel store; deduped the Shopify integration install path through `installBundle` and the fluid/flowmap pointer handling through a shared `usePointerInput`; split the 1k-line `ast-transforms.ts` into focused `helpers`/`remove-ops`/`add-ops`/`index` modules (behind a barrel re-export) and trimmed `setup-project.ts` under 1k; made bundle-key typing honest (`BundleId` + a `getBundle` accessor for the wider `RemovableId` space) and removed dishonestly-optional types (`CartLineItem.id`, `CartMeta`, a discriminated `CanvasContextValue`); and routed server-side integration env reads through the typed `@/lib/env` accessor. No consumer-facing API change.
- Next.js 16.3 preview: bumped `next` to the `preview` dist-tag, enabled top-level `partialPrefetching: true` (the 16.3 successor to `experimental.prefetchInlining`), removed the now-superseded `experimental.prefetchInlining` flag from the `experimental` block, and replaced the placeholder `app/loading.tsx` with an accessible skeleton shell (`role="status"`, `aria-busy="true"`, `sr-only` label, `animate-pulse` bars) demonstrating the instant-navigation loading pattern. Added throwaway, branch-only demo routes under `app/instant-nav-demo/` (hub + `fast`/`slow`/`[item]` routes exercising shell prefetch, partial streaming via `<Suspense>`/`connection()`, and a reused dynamic-route `loading.tsx` shell) so the preview deploy can actually test instant navigations; reachable at `/instant-nav-demo` and never merged to main. Also enabled the 16.3 Turbopack experimental flags `turbopackFileSystemCacheForBuild` (persistent build cache) and `turbopackRustReactCompiler` (native React Compiler, pairs with `reactCompiler: true`) to validate build-perf on the same preview.
- Dependencies: bumped every package to its latest version. The only breaking change was `tempus` dev.18, whose `TempusCallback` moved from positional `(time, deltaTime)` arguments to a single `TempusState` object (`{ time, deltaTime, frame, budget }`) and deprecated the `priority` option in favor of `order` — migrated all seven call sites (the GSAP, Lenis, marquee, and WebGL RAF runtimes, the `lib/utils/raf` write queue, and the dev `Stats` overlay). Also bumped `@base-ui/react` 1.6, `next-sanity` 13.1, `@types/node` 26, `deslop-cli` 0.5.8, `@biomejs/biome` 2.5.1, Storybook 10.4.6, Tailwind 4.3.1, and `@typescript/native-preview`; `actions/checkout` 6 → 7 in CI. Supersedes #251–254. (#255)
- Dependencies: bumped the Sanity toolchain to v6 (`sanity`, `@sanity/vision`, `groq` → `^6.1.0`) — Studio v6's breaking changes (Node 20 dropped, `auth.providers` replace semantics, `enableLegacySearch` removed) don't touch the integration's API surface, so forks on Node ≥ 22 update without code changes. Also bumped `@biomejs/biome` 2.5.0, `deslop-cli` 0.0.25, and `@typescript/native-preview`. Biome 2.5's newly-enabled rules required a `<title>` on `darkroom.svg` (`noSvgWithoutTitle`) and an import-spacing fix in generated `sanity.types.ts` (`organizeImports`); the inline version `<Script>` carries a documented `useInlineScriptId` ignore (the `id` is present — biome mis-detects JSX-children content). Supersedes #221–226. (#228)
- Integration registry is single-source: `INTEGRATION_BUNDLES` keys are typed against `RemovableId` from `lib/integrations/registry`, `prepare-handoff` matches integrations by id, and `next.config.ts` cleanup runs through typed ts-morph AST ops — the regex `updateNextConfig` is deleted.
Expand Down
16 changes: 16 additions & 0 deletions app/instant-nav-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# instant-nav-demo — throwaway

These routes exist ONLY on the `next/16.3-preview` branch to exercise Next.js
16.3 Instant Navigations (`partialPrefetching`) on a real deploy. They are NOT
starter content and must never merge to `main`.

- `page.tsx` — hub linking to the demo routes (prefetch fires on hover/viewport)
- `fast/` — synchronous route, the prefetched shell is the whole page
- `slow/` — shell paints immediately; an inner `<Suspense>` panel streams in
after a delay (`connection()` + artificial wait) — partial prerendering
- `[item]/` — dynamic route that suspends at the top level, so its `loading.tsx`
shell is shown on the way in and reused identically for every param value
(`/instant-nav-demo/alpha`, `/bravo`, `/charlie`)
Comment thread
arzafran marked this conversation as resolved.
Outdated

Reach it on the preview at `/instant-nav-demo`. Delete this whole folder before
adopting anything from this branch.
16 changes: 16 additions & 0 deletions app/instant-nav-demo/[item]/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default function Loading() {
return (
<div
role="status"
aria-busy="true"
className="flex min-h-screen flex-col items-center justify-center gap-3 font-mono"
>
Comment thread
Copilot marked this conversation as resolved.
Outdated
<span className="sr-only">Loading item</span>
<div className="w-40 animate-pulse space-y-2">
<div className="h-2 rounded bg-current opacity-20" />
<div className="h-2 w-3/4 rounded bg-current opacity-20" />
<div className="h-2 w-1/2 rounded bg-current opacity-10" />
</div>
</div>
)
}
38 changes: 38 additions & 0 deletions app/instant-nav-demo/[item]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { connection } from 'next/server'
import { Wrapper } from '@/components/layout/wrapper'
import { Link } from '@/components/ui/link'

export const metadata = {
title: 'Dynamic route — Instant Navigations demo',
}

export default async function ItemPage({
params,
}: {
params: Promise<{ item: string }>
}) {
const { item } = await params
await connection()
await new Promise((resolve) => setTimeout(resolve, 1200))
Comment thread
arzafran marked this conversation as resolved.

return (
<Wrapper theme="dark">
<section className="dr-layout-grid min-h-screen content-center py-24 font-mono">
Comment thread
Copilot marked this conversation as resolved.
Outdated
<div className="col-span-full dt:col-start-2 dt:col-end-11 flex flex-col gap-6">
<h1 className="text-2xl uppercase">Dynamic route: {item}</h1>
<p className="max-w-prose opacity-70">
One reusable shell is prefetched for this route and shown on the way
in (see the skeleton). It is identical for every param value —
<code> alpha</code>, <code>bravo</code>, and <code>charlie</code>.
Comment thread
arzafran marked this conversation as resolved.
Outdated
</p>
<Link
className="underline underline-offset-4"
href="/instant-nav-demo"
>
← Back to demo hub
</Link>
</div>
</section>
</Wrapper>
)
}
28 changes: 28 additions & 0 deletions app/instant-nav-demo/fast/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Wrapper } from '@/components/layout/wrapper'
import { Link } from '@/components/ui/link'

export const metadata = {
title: 'Fast route — Instant Navigations demo',
}

export default function FastPage() {
return (
<Wrapper theme="dark">
<section className="dr-layout-grid min-h-screen content-center py-24 font-mono">
Comment thread
Copilot marked this conversation as resolved.
Outdated
<div className="col-span-full dt:col-start-2 dt:col-end-11 flex flex-col gap-6">
<h1 className="text-2xl uppercase">Fast route</h1>
<p className="max-w-prose opacity-70">
Fully static — no async data. The prefetched shell IS the page, so
navigation here is immediate with nothing to stream.
</p>
<Link
className="underline underline-offset-4"
href="/instant-nav-demo"
>
← Back to demo hub
</Link>
</div>
</section>
</Wrapper>
)
}
53 changes: 53 additions & 0 deletions app/instant-nav-demo/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Wrapper } from '@/components/layout/wrapper'
import { Link } from '@/components/ui/link'

export const metadata = {
title: 'Instant Navigations demo',
}

const ITEMS = ['alpha', 'bravo', 'charlie']

export default function InstantNavDemoPage() {
return (
<Wrapper theme="dark">
<section className="dr-layout-grid min-h-screen content-center py-24 font-mono">
Comment thread
Copilot marked this conversation as resolved.
Outdated
<div className="col-span-full dt:col-start-2 dt:col-end-11 flex flex-col gap-6">
<h1 className="text-2xl uppercase">Instant Navigations demo</h1>
<p className="max-w-prose opacity-70">
Throwaway routes for exercising Next.js 16.3 shell-based
prefetching. Hover a link to prefetch its shell, then click to see
the shell paint instantly while data streams in.
</p>

<nav className="flex flex-col gap-3">
<Link
className="underline underline-offset-4"
href="/instant-nav-demo/fast"
>
→ Fast route (synchronous, paints immediately)
</Link>
<Link
className="underline underline-offset-4"
href="/instant-nav-demo/slow"
>
→ Slow route (shell first, panel streams in)
</Link>
{ITEMS.map((item) => (
<Link
key={item}
className="underline underline-offset-4"
href={`/instant-nav-demo/${item}`}
>
→ Dynamic route: {item} (one shell, reused per param)
</Link>
))}
</nav>

<Link className="underline underline-offset-4 opacity-60" href="/">
← Home
</Link>
</div>
</section>
</Wrapper>
)
}
58 changes: 58 additions & 0 deletions app/instant-nav-demo/slow/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { connection } from 'next/server'
import { Suspense } from 'react'
import { Wrapper } from '@/components/layout/wrapper'
import { Link } from '@/components/ui/link'

export const metadata = {
title: 'Slow route — Instant Navigations demo',
}

// connection() marks this subtree runtime-dynamic; the artificial delay makes
// the stream visible. Lives inside <Suspense> so the page shell paints
// immediately and only this panel streams in (partial prerendering under
// cacheComponents).
async function StreamedPanel() {
await connection()
await new Promise((resolve) => setTimeout(resolve, 1500))
return (
<p className="max-w-prose opacity-90">
Streamed in after ~1.5s. The heading and back-link above were already on
screen — only this panel waited on the server.
</p>
)
}

function PanelSkeleton() {
return (
<div aria-hidden className="w-40 animate-pulse space-y-2">
<div className="h-2 rounded bg-current opacity-20" />
<div className="h-2 w-3/4 rounded bg-current opacity-20" />
<div className="h-2 w-1/2 rounded bg-current opacity-10" />
</div>
)
}
Comment thread
arzafran marked this conversation as resolved.

export default function SlowPage() {
return (
<Wrapper theme="dark">
<section className="dr-layout-grid min-h-screen content-center py-24 font-mono">
Comment thread
Copilot marked this conversation as resolved.
Outdated
<div className="col-span-full dt:col-start-2 dt:col-end-11 flex flex-col gap-6">
<h1 className="text-2xl uppercase">Slow route</h1>
<p className="max-w-prose opacity-70">
The shell (this heading and the link below) paints instantly. The
panel streams in once the server resolves.
</p>
<Suspense fallback={<PanelSkeleton />}>
<StreamedPanel />
</Suspense>
<Link
className="underline underline-offset-4"
href="/instant-nav-demo"
>
← Back to demo hub
</Link>
</div>
</section>
</Wrapper>
)
}
13 changes: 11 additions & 2 deletions app/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@
// and fails prerendering (e.g. /studio). Keep this fallback dependency-free.
export default function Loading() {
return (
<div className="flex min-h-dvh flex-col items-center justify-center font-mono uppercase">
<p>Cooking...</p>
<div
role="status"
aria-busy="true"
className="flex min-h-dvh flex-col items-center justify-center gap-3 font-mono"
>
<span className="sr-only">Loading</span>
<div className="w-40 animate-pulse space-y-2">
<div className="h-2 rounded bg-current opacity-20" />
<div className="h-2 w-3/4 rounded bg-current opacity-20" />
<div className="h-2 w-1/2 rounded bg-current opacity-10" />
</div>
</div>
)
}
Loading
Loading