Skip to content

feat: add TanStack Start adapter#16717

Draft
r1tsuu wants to merge 42 commits into
mainfrom
feat/tanstack-adapter
Draft

feat: add TanStack Start adapter#16717
r1tsuu wants to merge 42 commits into
mainfrom
feat/tanstack-adapter

Conversation

@r1tsuu
Copy link
Copy Markdown
Member

@r1tsuu r1tsuu commented May 22, 2026

Squashed replay of the tanstack work previously developed on
experiment/framework-adapter-pattern, rebased onto refactor/admin-adapter-types.

Includes:

  • @payloadcms/tanstack-start package (RouterAdapter, ServerAdapter, auth
    server functions, server-function dispatcher, vite plugins, layouts/views)
  • tanstack-app test harness and e2e infrastructure
  • test/adapters/tanstackStartDevServer.ts + test/dev.ts dispatch case
  • Shared additions used by tanstack: payload/shared exports
    (extractAccessFromPermission, combineQueries, logError), payload "browser"
    conditional export pointing at exports/shared.ts, srvx-safe header/signal
    extraction in handleEndpoints, ASSET shims in packages/tanstack-start/src/https://github.com/types
  • Hydration-wait helpers and { framework: 'rsc' } test gating for e2e specs
  • Tanstack vite import-protection callback updated to match vite 7.x typings

For all shared files that both branches refactored differently
(packages/ui, packages/next, packages/payload), refactor/admin-adapter-types
is preserved; only genuinely tanstack-specific changes are layered on top.

r1tsuu and others added 30 commits May 11, 2026 17:58
Introduce framework-agnostic type contracts that decouple the admin panel
from Next.js-specific APIs, enabling alternative framework adapters.

Key changes:
- Add `admin/adapters.ts` with RouterAdapter, ServerAdapter, ComponentRenderer,
  DevReloadStrategy, and LinkAdapter type contracts
- Replace Next.js `Metadata` type with framework-agnostic `AdminMeta`
- Add `Plugin` type extensions (slug, order, options) and `PluginsMap`
- Add `renderComponent` to `ServerProps` for adapter-injected rendering
- Add `SidebarTab` type for extensible sidebar tab system
- Replace hardcoded Next.js HMR WebSocket with pluggable `DevReloadStrategy`
- Add `isRSCEnabled()` utility for RSC feature detection
- Add `ServerFunctionMode` to server function types
- Extend shared exports with `extractJWT`, error types, `canAccessAdmin`
- Update `loadEnv.ts` and `resolveImportMapFilePath` for adapter flexibility
Introduce PAYLOAD_FRAMEWORK env variable and switch dispatch in test/dev.ts
so the e2e and integration test runners can boot against pluggable framework
adapters. Extract the existing Next.js boot logic into
test/adapters/nextDevServer.ts behind a shared DevServerResult contract,
add the @payloadcms/ui/server path mapping, and add the framework adapter
pattern plan document.

- Add test/adapters/nextDevServer.ts (extracted from test/dev.ts)
- Rewrite test/dev.ts to dispatch on PAYLOAD_FRAMEWORK
- Add @payloadcms/ui/server path mapping in tsconfig.base.json
- Add docs/plans/framework-adapter-pattern.md
Phase 2 of the framework adapter pattern. Replace direct next/navigation
and next/link imports with a framework-agnostic RouterAdapterContext
defined in packages/ui, and provide the Next.js implementation in
packages/next.

- Add packages/ui/src/providers/RouterAdapter: framework-agnostic context
  exposing useRouter / useSearchParams / usePathname / Link primitives
- Add packages/next/src/elements/RouterAdapter: NextRouterAdapter that
  wires next/navigation hooks and next/link into the context

Subsequent commits will replace next/* imports across packages/ui with
this adapter and drop next from peerDependencies.
…I to packages/ui

Phase 3 of the framework adapter pattern. Move framework-agnostic admin
panel UI from packages/next to packages/ui and reduce packages/next to a
thin Next.js adapter. Replace next/* imports in packages/ui with the
RouterAdapter context introduced in the previous commit.

Major moves (packages/next -> packages/ui):
- elements/Nav, DocumentHeader, FormHeader, Logo
- templates/Default, templates/Minimal
- views/Login, ResetPassword, ForgotPassword, Unauthorized, Logout, Verify,
  CreateFirstUser (orchestrators)
- views/API, Account sub-components
- views/NotFound, Version, Versions client components
- widgets/CollectionCards (sync parts)

Other Phase 3 additions in packages/ui:
- utilities/routeResolution: framework-agnostic route matching + custom
  view resolution extracted from packages/next
- utilities/serverFunctionRegistry: shared registry of server function
  handlers consumable by any adapter
- New @payloadcms/ui/views/* exports for moved views

packages/next changes:
- Original element/template/view files reduced to thin re-exports of the
  packages/ui implementations
- handleServerFunctions uses the shared registry

Subsequent commit adds Phase 4 work (RSC abstraction, data-first pattern,
RenderServerComponent split, ComponentRenderer threading).
Phase 4 (partial) of the framework adapter pattern. Move the RSC
flight-path renderer out of packages/ui, extract data fetchers from view
components, and thread a pluggable ComponentRenderer through serverProps
so non-RSC adapters can render the same view tree with their own
rendering primitive.

Adapter boundary:
- Move RenderServerComponent's canonical implementation from packages/ui
  to packages/next/src/elements/RenderServerComponent
- Retain @payloadcms/ui/elements/RenderServerComponent as a deprecated
  re-export and add @payloadcms/ui/elements/RenderServerComponent/clientOnly
  exporting the framework-agnostic RenderClientComponent
- Add packages/ui/src/exports/server.ts entrypoint for server-only utils
- Add @payloadcms/next/elements/RenderServerComponent export

Data-first view pattern (extract async data fetchers from packages/ui views
and pair them with index.client.tsx client components — orchestrators in
packages/next call the fetcher then render the client component):
- Root, Dashboard, Account, Login, Document, List, Version, Versions,
  Verify, CreateFirstUser data fetchers
- Nav.getNavData, CollectionCards data fetcher
- ModularDashboard data fetcher + client component

Server-function data-only path:
- Add packages/ui/src/utilities/dataOnlyHandlers/* and
  dataOnlyServerFunctions.ts so non-RSC adapters can call render-document,
  render-list, render-widget, render-field, render-document-slots,
  getDefaultLayout and receive JSON (no React flight payload)

ImportMap as a client provider:
- Move ImportMap into packages/ui/src/providers/ImportMap so adapters
  hydrate the import map through a framework-agnostic context

richtext-lexical:
- Adopt the injected ComponentRenderer in rscEntry and generateImportMap
- Add browser-safe rsc.browser entry and clientEntry for non-RSC adapters
The Phase 1 commit replaced @next/env with dotenv + dotenv-expand in
loadEnv.ts but did not update package.json. Drop @next/env, add the
dotenv packages, and refresh the lockfile.
Reconcile main's UI4 css migrations and view redesigns (NotFound,
CreateFirstUser, Login, Unauthorized) with Phase 3 element/template/view
moves from packages/next to packages/ui. Pull main's css into the moved
ui locations, drop the original scss files, and keep main's redesigns
as the canonical implementation behind thin next-side re-exports. Take
main's Document/index.tsx orchestrator as-is to avoid regressing
Next.js behavior; the data-first refactor stays available in
packages/ui for non-Next adapters but does not back the Next view.
When buildFormState was called without an explicit renderComponent
(the Next.js path), the fallback was RenderClientComponent which strips
serverProps from RSC components. This dropped clientField on RSC field
renderers (e.g. lexical's RscEntryLexicalField), producing
'Initialized lexical RSC field without a field name'. Default to the
RSC-aware RenderServerComponent so Next.js form-state builds keep
working; non-Next adapters still inject their own ComponentRenderer
explicitly.

Also flip @payloadcms/ui exports from index.scss to index.css for the
view paths Phase 3 moved next->ui (Login, LoginForm, CreateFirstUser,
Unauthorized) to match main's UI4 css conversions.
Phase 3 auto-created next-side stubs that re-export from @payloadcms/ui
for every moved element/template/view. 64 of these are never imported
from anywhere — neither internally in packages/next, nor from other
packages, nor via @payloadcms/next/* package.json exports. Delete them.

Kept: thin re-exports that are still imported by next-side orchestrators
(Document/index.tsx, Account/index.tsx, etc.) via relative paths.
Removing those would require adding new @payloadcms/ui/* package.json
exports for each (e.g. ./views/Document/getDocPreferences); deferred.

Breaking change for external consumers doing deep imports under
@payloadcms/next/elements, /templates, or /views — none of these paths
are in packages/next/package.json's exports field, so they were
unsupported deep imports.
… directly

Replace 34 thin re-export files in packages/next/src with direct
@payloadcms/ui/* imports in the orchestrators and exports/* barrels.
Add the 8 missing @payloadcms/ui package.json exports needed for these
direct imports (Document/getDocPreferences|getDocumentData|getIsLocked
|getVersions, List/enrichDocsWithVersionStatus|resolveAllFilterOptions
|transformColumnsToSelect, elements/DocumentHeader).

Net effect: packages/next contains only orchestrators + adapter-specific
files (RouterAdapter, RenderServerComponent). All framework-agnostic
elements/templates/views are consumed from @payloadcms/ui directly.

Breaking change for deep imports under @payloadcms/next/{elements,
templates,views} — those paths were never in package.json exports
field, so they were already unsupported.
TS2882 from tsc declaration emit on packages with side-effect CSS/SCSS
imports. Added `declare module '*.css'` + '*.scss' shims to next,
plugin-ecommerce, plugin-import-export, plugin-multi-tenant, and
richtext-lexical.
TS2307 from tsc declaration emit on `next/font/google` / `next/font/local`
in CI. Follows same pattern as css/scss shim.
TS2882 from tsc declaration emit on MetaTitleComponent side-effect SCSS
import. Same pattern as dd8fa14.
Auto-fixed via `eslint --fix`. No behavior changes.
Regression from packages/next adapter restructure: getDocumentView call
in renderDocument lost defaultViews arg, so FallbackVersionsView,
FallbackVersionView, and FallbackEditView resolved to null/defaults.
For the Versions and Version sub-routes the result was NotFoundView ->
"Nothing found" page, even though the underlying data fetch succeeded.

Restore defaultViews wiring with EditView, VersionView, VersionsView
from packages/next/views.
Refactor 4210a01 stripped the getHTMLDiffComponents wrapping from
upload field diffs, which removed the .html-diff__diff-old /
.html-diff__diff-new classnames that test selectors and SCSS rely on.
Re-add the wrapping divs (without the ReactDOMServer roundtrip) so
the From/To columns are addressable by the same classnames as text /
textArea diffs.
renderDocumentSlots defaults its renderComponent arg to
RenderClientComponent, which strips serverProps. When invoked from the
Next.js Document view, slot components like EditMenuItems are RSCs and
need server props (id, payload, user, etc.) to render correctly.

Without this, custom editMenuItems server components received
`props.id === undefined`. Mirrors the same regression class as
66a76ae (buildFormState renderComponent).

Also switch the import from the deprecated @payloadcms/ui re-export to
the @payloadcms/next-local RenderServerComponent.
The packages/next -> packages/ui Nav refactor dropped the
`nav__link--selected` modifier from the active link element, leaving
only the `__link-indicator` child div. E2E tests assert presence of the
selected modifier on the link itself for active-state checks (also
useful for styling hooks that match the link element directly).
The packages/next -> packages/ui Account view move kept the
ToggleHighContrast component in packages/next but the moved Settings
component (now in packages/ui) only renders ToggleTheme. The
/admin/account view was missing the high-contrast checkbox entirely.

Move ToggleHighContrast into packages/ui alongside ToggleTheme (it
already depends only on the framework-agnostic useTheme provider) and
render it from Settings. Drop the now-unused packages/next copy.
…sertion

- QueryPresetBar: after the document drawer saves the active preset,
  call router.refresh() so the RSC re-fetches the updated preset doc.
  handlePresetChange only mutates URL params, so when only non-URL
  fields change (e.g. title) the existing flow leaves the cached
  activePreset prop stale and the trigger button keeps showing the old
  title.

- clearGroupBy helper / preset reset spec: when a query preset is
  active, sanitizeQuery intentionally preserves an empty `groupBy=`
  param as the user's clear-override marker (see 594d3c8bf4). Relax
  the assertions from "groupBy must be absent" to "groupBy must not
  carry a value" so they pass in both preset-active and preset-absent
  contexts.
The packages/next -> packages/ui Nav refactor extracted getNavData and
made it read `req.payload.config`, but several legitimate callers
(notably custom root views built on top of DefaultTemplate, e.g.
CustomDefaultView in the admin test suite) render DefaultTemplate
without forwarding a `req`. Before the refactor the equivalent inline
code read `payload.config` directly and only `getNavPrefs(req)`
touched req, so the path tolerated a missing req.

Take `payload` explicitly, treat `req` as optional, and fall back to
an empty NavPreferences when req isn't available. Update the
packages/next DefaultNav adapter to forward the already-destructured
payload.
Two regressions from the packages/next -> packages/ui Nav refactor:

1. The ui DefaultNav drops the SidebarTabs wrapper. Any feature that
   adds a sidebar tab via admin.components.sidebar.tabs (hierarchy,
   plugins) used to surface as a tab in the nav; after the refactor
   only the collections list rendered. Rebuild the next DefaultNav to
   wrap DefaultNavClient in SidebarTabs the same way the original
   next-side Nav did, while keeping the framework-agnostic ui Nav as
   the fallback for non-next adapters.

2. The simplified ui getNavPrefs returned null when no preference doc
   exists; SidebarTabs then dereferences `.activeTab` on the
   undefined navPreferences and crashes the server render. Restore
   the original behaviour: always resolve to a NavPreferences object
   with defaults and merge in the separately-stored sidebar active
   tab preference.
The packages/next -> packages/ui renderDocumentSlots refactor lost the
BeforeDocumentMeta slot wiring. Features that inject components via
admin.components.edit.BeforeDocumentMeta (most visibly the hierarchy
plugin's folder header button injected by injectHierarchyButton) never
mount, so the doc header is missing the folder picker entirely and any
e2e that expects to click "find child folder" times out.

Re-emit BeforeDocumentMeta from the RSC renderer alongside
BeforeDocumentControls, mirroring the original next-side implementation,
and add the same slot to the data-only handler / DocumentSlotConfigs.
The Nav rewrite stopped importing the ui DefaultNav component (which
side-effect-imported its scss) and didn't replace it with an equivalent
side-effect import for packages/next/src/elements/Nav/index.css. As a
result .nav__scroll lost its height/overflow rules and .nav__wrap /
.nav__controls lost their flex layout, so the settings menu button
rendered far below the viewport and was unclickable in tests.

Re-add the local index.css side-effect import.
The CustomListDrawer e2e test waits for the success toast as its
synchronization point, then closes the document drawer and asserts the
outer list drawer now shows two rows. The original handler fired the
toast immediately after the POST and only then awaited refresh(), so
in slower environments the test could close the drawer and check rows
before refresh() resolved.

Reorder so refresh() resolves first; toast then acts as a deterministic
signal that the outer list view is up to date.
The ListDrawer refresh path previously cleared the currently rendered
list view (setListView(null)) when the server function returned an
unexpected shape, and force-closed the drawer outright when it threw.
Both behaviors are fine for the very first load, when there is no list
to preserve and the drawer cannot show without data. They are too
destructive for refreshes triggered by user actions (e.g. the
"create new" button from a nested CustomListDrawer in a doc drawer):
a transient server-function error or empty payload would silently
nuke the open list view (and any nested document drawer inside it).

Track whether the drawer has ever successfully rendered a list view
via hasLoadedRef. After the first success, treat missing/error
results as a no-op and let the existing view remain visible.
The "should refresh custom list drawer using the refresh method from
context" e2e test was racing in CI: the test would click the inner
CustomListDrawer create button and immediately assert that the toast
appeared. In environments where the POST took noticeable time the
toast hadn't fired (and the sonner container hadn't lazily mounted)
before the 18s timeout, producing "element(s) not found" on
.payload-toast-container.

Anchor on the POST response with page.waitForResponse so the toast
assertion only begins after the create handler has actually finished
its network work.
CI snapshot for the "should refresh custom list drawer using the
refresh method from context" e2e shows the outer list drawer was
correctly refreshed to 2 rows but the .payload-toast-container
element was missing entirely. sonner only mounts its <ol> while at
least one toast is visible; with the default 4s duration, the toast
can auto-dismiss before the toast assertion runs when CI is slow.

Pin the duration on this specific toast to 30s so the assertion has
ample time to find the rendered container regardless of CI scheduling
jitter.
@r1tsuu r1tsuu changed the title feat(tanstack-start): add TanStack Start adapter feat: add TanStack Start adapter May 22, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 22, 2026

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 221.31 KB ✅ -805.35 KB (-78.4%)
packages/payload/meta_index.json esbuild/index.js 1.41 MB ⚠️ +197 B (+0.0%)
packages/payload/meta_shared.json esbuild/exports/shared.js 212.79 KB ⚠️ +20.28 KB (+10.5%)
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 305.88 KB ⚠️ +1.75 KB (+0.6%)
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 63.52 KB ✅ -1.21 MB (-95.0%)
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 18.59 KB ⚠️ +38 B (+0.2%)
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ███████████████████████▉ }}}$ 95.5%, 209.67 KB
dist/layouts/Root ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 3.81 KB
dist/admin/views.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 1.24 KB
dist/utilities/initReq.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 1.15 KB
dist/admin/RootPage.js ${{\color{Goldenrod}{ }}}$ 0.4%, 800 B
dist/adapters/router.js ${{\color{Goldenrod}{ }}}$ 0.3%, 663 B
dist/adapters/server.js ${{\color{Goldenrod}{ }}}$ 0.2%, 533 B
dist/utilities/getRequestLocale.js ${{\color{Goldenrod}{ }}}$ 0.2%, 452 B
dist/utilities/getRequestTheme.js ${{\color{Goldenrod}{ }}}$ 0.2%, 369 B
dist/utilities/getRequestHighContrast.js ${{\color{Goldenrod}{ }}}$ 0.1%, 260 B
dist/utilities/selectiveCache.js ${{\color{Goldenrod}{ }}}$ 0.1%, 253 B
dist/utilities/getPreferences.js ${{\color{Goldenrod}{ }}}$ 0.1%, 239 B
dist/esbuildEntry.js ${{\color{Goldenrod}{ }}}$ 0.0%, 0 B

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ █████████████████ }}}$ 68.4%, 959.51 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▊ }}}$ 3.1%, 44.07 KB
dist/collections/operations ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 40.23 KB
dist/versions/migrations ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 18.50 KB
dist/auth/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 15.63 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.40 KB
dist/globals/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.40 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 13.10 KB
dist/queues/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 12.63 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 10.66 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.53 KB
dist/bin/generateImportMap ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.41 KB
dist/index.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.96 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.92 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.80 KB
dist/hierarchy/utils ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 7.65 KB
dist/database/migrations ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 7.54 KB
dist/collections/endpoints ${{\color{Goldenrod}{ }}}$ 0.4%, 6.23 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 5.43 KB
dist/queues/config ${{\color{Goldenrod}{ }}}$ 0.4%, 5.31 KB
(other) ${{\color{Goldenrod}{ ███████▉ }}}$ 31.6%, 443.47 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████████████ }}}$ 72.1%, 150.13 KB
dist/fields/validations.js ${{\color{Goldenrod}{ █▎ }}}$ 5.1%, 10.66 KB
dist/fields/config ${{\color{Goldenrod}{ ▋ }}}$ 2.8%, 5.84 KB
dist/utilities/traverseFields.js ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 4.53 KB
dist/collections/config ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 3.21 KB
dist/config/orderable ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 3.13 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 2.79 KB
dist/config/client.js ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 2.68 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 2.54 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.41 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 1.04 KB
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 942 B
dist/globals/config ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 939 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ }}}$ 0.4%, 779 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.3%, 713 B
dist/auth/extractJWT.js ${{\color{Goldenrod}{ }}}$ 0.3%, 696 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 651 B
dist/errors/ValidationError.js ${{\color{Goldenrod}{ }}}$ 0.3%, 577 B
dist/bin/generateImportMap ${{\color{Goldenrod}{ }}}$ 0.3%, 561 B
(other) ${{\color{Goldenrod}{ ██████▉ }}}$ 27.9%, 58.24 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███ }}}$ 12.3%, 37.36 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▊ }}}$ 11.3%, 34.16 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▋ }}}$ 10.9%, 32.88 KB
dist/features/experimental_table ${{\color{Goldenrod}{ ██▎ }}}$ 9.0%, 27.16 KB
dist/packages/@lexical ${{\color{Goldenrod}{ █▌ }}}$ 6.3%, 18.99 KB
dist/features/link ${{\color{Goldenrod}{ █▌ }}}$ 6.2%, 18.81 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▍ }}}$ 5.5%, 16.58 KB
dist/features/upload ${{\color{Goldenrod}{ █▏ }}}$ 4.7%, 14.09 KB
dist/features/textState ${{\color{Goldenrod}{ ▉ }}}$ 3.7%, 11.08 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.1%, 9.39 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 8.79 KB
dist/features/converters ${{\color{Goldenrod}{ ▋ }}}$ 2.8%, 8.36 KB
dist/features/debug ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 7.40 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 7.29 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 5.14 KB
dist/features/lists ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 5.00 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 3.55 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.46 KB
dist/features/horizontalRule ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.18 KB
dist/field/Field.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.84 KB
(other) ${{\color{Goldenrod}{ █████████████████████▉ }}}$ 87.7%, 265.25 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████████▋ }}}$ 58.6%, 32.35 KB
dist/exports/client ${{\color{Goldenrod}{ ██████████▎ }}}$ 41.4%, 22.86 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ ███████▊ }}}$ 31.0%, 5.57 KB
../../node_modules ${{\color{Goldenrod}{ ███▋ }}}$ 14.8%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██▏ }}}$ 8.5%, 1.52 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ █▊ }}}$ 7.4%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▏ }}}$ 4.8%, 866 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █ }}}$ 4.3%, 772 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 762 B
dist/utilities/api.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 756 B
dist/elements/Translation ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 440 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 399 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 339 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 338 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 329 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 232 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 159 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
dist/forms/Form ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 147 B
dist/utilities/abortAndIgnore.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 146 B
(other) ${{\color{Goldenrod}{ █████████████████▎ }}}$ 69.0%, 12.40 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

@r1tsuu r1tsuu changed the base branch from main to refactor/admin-adapter-types May 22, 2026 09:18
…ter refactor

Squashed replay of the tanstack work previously developed on
experiment/framework-adapter-pattern, rebased onto refactor/admin-adapter-types.

Includes:
- @payloadcms/tanstack-start package (RouterAdapter, ServerAdapter, auth
  server functions, server-function dispatcher, vite plugins, layouts/views)
- tanstack-app test harness and e2e infrastructure
- test/adapters/tanstackStartDevServer.ts + test/dev.ts dispatch case
- Shared additions used by tanstack: payload/shared exports
  (extractAccessFromPermission, combineQueries, logError), payload "browser"
  conditional export pointing at exports/shared.ts, srvx-safe header/signal
  extraction in handleEndpoints, ASSET shims in packages/tanstack-start/src/@types
- Hydration-wait helpers and { framework: 'rsc' } test gating for e2e specs
- Tanstack vite import-protection callback updated to match vite 7.x typings

For all shared files that both branches refactored differently
(packages/ui, packages/next, packages/payload), refactor/admin-adapter-types
is preserved; only genuinely tanstack-specific changes are layered on top.
@r1tsuu r1tsuu force-pushed the feat/tanstack-adapter branch from ad851a4 to 087ecc2 Compare May 22, 2026 09:25
r1tsuu added 2 commits May 22, 2026 14:13
… view

Parse the URL's query string when initializing `req` so `?locale=` and other
query params reach access control and `getRequestLocale`. Surface query
params via `validateSearch`/`loaderDeps` so route loaders re-run on locale
changes. Pipe `?locale=` to `loadDashboard` and use `getRequestLocale` in
the server function handler.

Add a `DocumentViewKind` flag to serialized document data so the client can
render the matching fallback when the server resolves to
`UnauthorizedViewWithGutter`. Introduce a client-only `UnauthorizedView` to
avoid sharing the server view's `req.i18n` dependency.

Extend `buildTableState` with a `data-only` mode that returns serializable
table data, and add `buildTableStateClientProps` to reconstruct
`{state, Table}` on the client. `RelationshipTable` uses this fallback so
the same component works across RSC and non-RSC adapters.

Skip TanStack hydration wait for non-admin URLs (e.g. `/api/...`) to avoid
paying the 15s timeout per request. Skip two localization e2e tests on the
TanStack adapter with notes on the underlying gaps.
@jacobsfletch jacobsfletch force-pushed the refactor/admin-adapter-types branch from 149a4c4 to 709d8f4 Compare May 27, 2026 19:16
r1tsuu added 6 commits May 28, 2026 12:16
Move Next.js admin view RSCs (Account, CreateFirstUser, Dashboard,
Document, List, Login, Verify, Version, Versions) into
`@payloadcms/ui/views/*/.../*ViewRSC` and dispatch them through a single
shared `renderAdminView` helper. The next adapter is now thin glue
(~225 line `RootPage`) that translates `not-found` / `redirect:<url>`
errors thrown by the shared views into Next's native `notFound()` /
`redirect()`. Co-locates `RenderServerComponent`, `getRouteData`,
`Nav` + `SidebarTabs`, default templates, and list hierarchy helpers in
ui so the tanstack-start adapter can consume the same RSCs.

Per docs/plans/unify-adapter-views.md (Stages 0–3).
…SC payloads

Server-rendered custom field components (e.g. those produced by
`buildFormState` + `RenderServerComponent`) used to be silently stripped
by the TanStack adapter's `JSON.stringify(toSerializable(...))` pipeline,
so newly added array rows showed no `customComponents.Field` until
save+reload. Next.js doesn't have this problem because Server Action
responses already use the RSC payload format.

Switch the TanStack `form-state` (and every other shared server function)
to the same RSC payload wire format:

- Add `serializeForRsc` which walks a server-function result and converts
  every React element into a "renderable RSC handle" via
  `renderServerComponent` from `@tanstack/react-start/rsc`. TanStack
  Start's `$RSC` serialization adapter then streams the underlying Flight
  bytes inline within the createServerFn response and the client decodes
  each handle back into a renderable React proxy. Plain JSON data still
  rides seroval. Mirrors `toSerializable`'s walk for circular refs, Maps,
  Sets, Dates, typed arrays; strips functions / Symbols / RegExps.
- Pipe the package-level `handleServerFunctions` result through
  `serializeForRsc` so any caller that wires the dispatcher into a
  TanStack RPC context (i.e. createServerFn) gets RSC handles for free.
- Replace the hand-rolled `/api/server-function` route + fetch handler
  with `runPayloadServerFn`, a `createServerFn({ method: 'POST' })` that
  invokes `handleServerFunctions`. The client `serverFunctionHandler`
  delegates to it; arguments are stripped of unserializable values
  (functions / Symbols / RegExps / React elements) on the way in to
  preserve the relaxed JSON.stringify behaviour callers depended on.
- Update `assertNetworkRequests` and the auth + lexical e2e specs to
  recognise TanStack Start's `/_serverFn/<base64-fn-id>` createServerFn
  RPC URL alongside the legacy `/api/server-function` path.

Verified against the original repro: `_community` with the `TitleField`
server component nested under an `array` field. Before: new rows showed
no character-count line until a full save+reload. After: it renders
immediately on Add Row (`array.0.title`, `array.1.title`, ...) and
updates correctly after save (`Character count: 14` for "first row
text").
…cument handlers across adapters

Stage 4 + 4.5 of the unify-adapter-views plan. Removes the duplicate
data-only rendering paths (per-adapter handlers, client-side rebuilders,
and `getAdminPageData` in the TanStack adapter) now that both Next and
TanStack Start ship admin views as RSC Flight payloads.

- Delete `packages/tanstack-start/src/views/` (`AdminView.tsx`,
  `AccountSettings/`, `Root/index.tsx`, `Root/getRouteData.ts`) and the
  `./views` / `./views/server` package exports. The TanStack admin route
  now goes through the shared `renderAdminPage` helper from
  `@payloadcms/ui` via `loadAdminPageRSC`.
- Delete the `tanstack-app` glue that wrapped the legacy pipeline:
  `admin.functions.tsx` (`loadAdminPage` / `loadDashboard`),
  `getToSerializable.ts`, and `components/AdminPageView/`.
- Promote `render-list` / `render-document` server-function handlers
  from `packages/next/src/admin/serverFunctions/` into
  `packages/ui/src/utilities/sharedHandlers/`, registered via
  `sharedServerFunctions` in `serverFunctionRegistry.ts`. Both Next and
  TanStack Start `handleServerFunctions.ts` consume the shared registry;
  the TanStack-side merge of a separate `dataOnlyServerFunctions`
  registry is removed.
- Delete `packages/ui/src/utilities/dataOnlyHandlers/` (all 6 files) and
  `dataOnlyServerFunctions.ts`.
- Delete the data-only client-side rebuilders
  (`buildListViewClientProps`, `buildDocumentViewClientProps`,
  `toSerializableListViewData`, `buildTableStateClient`) and the
  matching `data-only` branches in `buildTableState`,
  `RelationshipTable`, `ListDrawer/DrawerContent`, and
  `DocumentDrawer/DrawerContent`. Drop the corresponding subpath exports
  from `packages/ui/package.json`.
- Doc: update `docs/plans/unify-adapter-views.md` checklist for stages
  4, 4.5.2, 4.5.3, and 4.5.4 (4.5.4 is N/A — `getAdminPageData` was
  deleted wholesale in Stage 4).
- Misc: fix `test/_community/onInit` query/create to match the nested
  `array.title` schema; update the `tanstack-app` dev script to use
  `vite dev --configLoader runner` so the workspace `.ts` source for
  `@payloadcms/tanstack-start/vite` resolves correctly.

Validated: tanstack-app boots cleanly; the `_community` Add-Row
scenario now renders the custom `TitleField` server component
(`Character count: ...`) immediately for newly inserted array rows,
which was the original motivating bug for unifying the wire format.
…ws architecture

Stage 6 of the unify-adapter-views plan.

- `CLAUDE.md`: new "Architecture: shared views, thin adapters" section
  under Admin Panel describing the `renderAdminPage` dispatcher,
  shared `*ViewRSC`s in `@payloadcms/ui`, the `sharedServerFunctions`
  registry, and the 5-step contract for new adapters. Calls out
  the RSC-Flight-only wire format for server-function renders.
- New `docs/framework-adapter-guide.md`: one-page contributor guide
  covering `initReq`, the admin entry point, server-function
  dispatch, layout/providers, the `NavigationAdapter` contract,
  reference implementations, and an explicit "what you do NOT do"
  list (no view forks, no `getAdminPageData`, no data-only wire
  format, no client-side rebuilders).
- `docs/plans/unify-adapter-views.md`: tick Stage 6.
…pter-views

Mark Stage 4.5.5 and 4.6 as partially complete with explicit notes:
- Stage 4.5.5: local _community smoke confirms the Document RSC,
  add-row form-state refetch, and not-found redirect contract all
  flow through the unified RSC-Flight pipeline; remaining buckets
  (copy-from-locale, drawers, plugin-seo) ride the same shared
  handlers and are deferred to Stage 4.6 multi-suite runs.
- Stage 4.6: full TanStack e2e matrix is CI-bound. Local checks done:
  lint clean, ui/next/tanstack-start packages build, _community
  Add-Row green. The hierarchy / document-view / versions buckets
  flagged in `E2E_TEST_RESULTS.md` need a CI run on this branch.
# Conflicts:
#	packages/next/package.json
#	packages/next/src/admin/NotFoundPage.tsx
#	packages/next/src/elements/Nav/NavSidebarToggle/index.tsx
#	packages/next/src/exports/client.ts
#	packages/next/src/exports/rsc.ts
#	packages/next/src/exports/templates.ts
#	packages/next/src/layouts/Root/index.tsx
#	packages/next/src/utilities/handleServerFunctions.ts
#	packages/next/src/views/Account/index.tsx
#	packages/next/src/views/Dashboard/Default/index.tsx
#	packages/next/src/views/Dashboard/index.tsx
#	packages/next/src/views/Login/index.tsx
#	packages/next/src/views/Root/index.tsx
#	packages/next/src/views/Verify/index.tsx
#	packages/next/src/views/Versions/index.tsx
#	packages/payload/src/config/sanitize.ts
#	packages/payload/src/config/types.ts
#	packages/payload/src/hierarchy/resolveHierarchyCollections.ts
#	packages/payload/src/index.ts
#	packages/richtext-lexical/src/index.ts
#	packages/ui/src/elements/CheckboxPopup/index.scss
#	packages/ui/src/elements/DocumentHeader/Tabs/Tab/index.scss
#	packages/ui/src/elements/DocumentHeader/Tabs/Tab/index.tsx
#	packages/ui/src/elements/DocumentHeader/Tabs/index.scss
#	packages/ui/src/elements/DocumentHeader/Tabs/index.tsx
#	packages/ui/src/elements/DocumentHeader/Tabs/tabs/VersionsPill/index.scss
#	packages/ui/src/elements/DocumentHeader/Tabs/tabs/VersionsPill/index.tsx
#	packages/ui/src/elements/DocumentHeader/index.tsx
#	packages/ui/src/elements/Loading/index.scss
#	packages/ui/src/elements/Logo/index.tsx
#	packages/ui/src/elements/Nav/NavHamburger/index.tsx
#	packages/ui/src/elements/Nav/NavSidebarToggle/index.tsx
#	packages/ui/src/elements/Nav/SidebarTabs/index.tsx
#	packages/ui/src/elements/Nav/getNavPrefs.ts
#	packages/ui/src/elements/Nav/index.client.tsx
#	packages/ui/src/elements/Nav/index.tsx
#	packages/ui/src/providers/Root/index.tsx
#	packages/ui/src/providers/RouterAdapter/index.tsx
#	packages/ui/src/templates/Default/index.tsx
#	packages/ui/src/views/Dashboard/Default/ModularDashboard/WidgetConfigDrawer.tsx
#	packages/ui/src/views/Dashboard/Default/ModularDashboard/index.css
#	packages/ui/src/views/Document/DocumentViewRSC.tsx
#	packages/ui/src/views/Document/renderDocumentSlots.tsx
#	packages/ui/src/views/ForgotPassword/ForgotPasswordForm/index.tsx
#	packages/ui/src/views/ForgotPassword/index.tsx
#	packages/ui/src/views/List/index.tsx
#	packages/ui/src/views/ResetPassword/index.tsx
#	packages/ui/src/views/Version/VersionPillLabel/VersionPillLabel.tsx
#	packages/ui/src/views/Version/VersionPillLabel/index.css
#	packages/ui/src/views/Version/VersionPillLabel/index.scss
#	packages/ui/src/views/Versions/cells/AutosaveCell/index.css
#	packages/ui/src/views/Versions/cells/AutosaveCell/index.scss
#	packages/ui/src/views/Versions/index.client.tsx
#	packages/ui/src/views/Versions/index.css
#	packages/ui/src/views/Versions/index.scss
#	packages/ui/src/widgets/CollectionCards/index.tsx
#	pnpm-lock.yaml
#	test/__helpers/e2e/groupBy/clearGroupBy.ts
#	test/admin/components/views/CustomDefault/index.tsx
#	test/admin/components/views/CustomMinimal/index.tsx
#	test/admin/e2e/list-view/e2e.spec.ts
#	test/fields/collections/Collapsible/e2e.spec.ts
#	test/fields/collections/Email/e2e.spec.ts
#	test/fields/collections/JSON/e2e.spec.ts
#	test/fields/collections/Radio/e2e.spec.ts
#	test/fields/collections/Tabs/e2e.spec.ts
#	test/fields/collections/Text/e2e.spec.ts
#	test/fields/collections/Upload/e2e.spec.ts
#	test/group-by/payload-types.ts
#	test/live-preview/payload-types.ts
#	test/query-presets/e2e.spec.ts
#	test/sort/payload-types.ts
@r1tsuu r1tsuu changed the base branch from refactor/admin-adapter-types to main May 29, 2026 15:28
# Conflicts:
#	test/__helpers/e2e/columns/openListColumns.ts
#	test/__helpers/e2e/helpers.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants