mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[PR #747] [MERGED] feat: add client primitives for layout persistence (Slot, Children, mergeElementsPromise) #814
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#814
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/cloudflare/vinext/pull/747
Author: @NathanDrake2406
Created: 4/2/2026
Status: ✅ Merged
Merged: 4/2/2026
Merged by: @james-elicx
Base:
main← Head:feat/layout-persistence-pr-2a📝 Commits (3)
5d8525bAdd slot client primitivesaed3514fix: address review feedback on slot primitives6a2aa52fix: prototype pollution in Slot, freeze shared default, improve tests📊 Changes
2 files changed (+351 additions, -0 deletions)
View changed files
➕
packages/vinext/src/shims/slot.tsx(+85 -0)➕
tests/slot.test.ts(+266 -0)📄 Description
Summary
Part of #726 (PR 2a). Adds the client-side composition primitives that the flat keyed map architecture needs. These are purely additive — no existing code consumes them yet. PR 2c ("The Switch") wires them into the browser/SSR entries.
New file:
packages/vinext/src/shims/slot.tsxFour
"use client"exports that implement the Waku-inspired slot/children pattern:Slot— The core composition primitive. Reads an entry from the sharedPromise<Elements>map by ID, provides children and parallel slots via context. Handles three states:UNMATCHED_SLOTsentinel → callnotFound()(nodefault.jsexists)null) → render normallyChildren— How layouts render their child content. Reads fromChildrenContext, whichSlotsets when rendering a layout entry. This maintains Next.js's{children}prop API.ParallelSlot— How layouts render named parallel slots (@modal,@sidebar). Reads fromParallelSlotsContext.mergeElementsPromise— The layout persistence mechanism. Shallow-merges new entries into existing ones with a double-nested WeakMap cache for referential stability (same(prev, next)promise pair always returns the same output Promise — critical for avoiding unnecessary React re-renders viause()).Design decisions
UNMATCHED_SLOTusesSymbol.for("vinext.unmatchedSlot")instead ofnull— becausedefault.tsxcan legitimately returnnull(e.g.,feed/@modal/default.tsx). Usingnullas the sentinel would make it impossible to distinguish "render default.js which returned null" from "no default.js exists." See design discussion in #726.RSC wire format note: Symbols can't survive React's flight serialization. PR 2c handles the translation — the server uses a serializable marker, and the client translates to the Symbol after
createFromFetch(). TheSlotcomponent always checks against the Symbol, never the wire format.Contexts
ElementsContextBrowserRoot(PR 2c)SlotChildrenContextnullSlotChildrenParallelSlotsContextnullSlot(whenparallelSlotsprop provided)ParallelSlotTest plan
New test file:
tests/slot.test.ts(10 tests)Childrenrenders null outside aSlotproviderParallelSlotrenders null outside aSlotproviderSlotrenders matched element and provides children + parallel slots via contextSlotreturns null for absent entries (soft nav persistence)SlotthrowsnotFound()forUNMATCHED_SLOTsentinel (with correct digest)Slotrendersnullentries without triggeringnotFound(default.tsx returning null)mergeElementsPromiseshallow-merges with correct key semanticsmergeElementsPromisecaches by input promise identity (WeakMap memoization)Slotsuspends on pending elements promise and streams with Suspense fallbackRefs #726
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.