Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
9d7173f
commit plan
r1tsuu Mar 31, 2026
2c72dfd
add example
r1tsuu Mar 31, 2026
450f03a
feat: define AdminAdapter interface, helper, and lifecycle wiring
r1tsuu Apr 1, 2026
b1bfbb2
feat(ui): add RouterProvider context and integrate into RootProvider
r1tsuu Apr 1, 2026
7169001
refactor: decouple core packages from Next.js and implement nextAdapter
r1tsuu Apr 1, 2026
dce2d02
refactor: split views, refactor client components, add ServerNavigation
r1tsuu Apr 1, 2026
c9d1205
refactor: move 8 framework-agnostic views from packages/next to packa…
r1tsuu Apr 1, 2026
19592ba
refactor: move Dashboard view and utilities to packages/ui
r1tsuu Apr 1, 2026
61b622f
feat(create-payload-app): add --framework flag and framework selectio…
r1tsuu Apr 1, 2026
4dceb9e
feat: scaffold @payloadcms/tanstack-start adapter package
r1tsuu Apr 1, 2026
59533d9
test: add AdminAdapter integration tests
r1tsuu Apr 1, 2026
a9ceee3
test: add FrameworkTestHarness interface for multi-framework e2e support
r1tsuu Apr 1, 2026
93b05fc
docs: add TanStack Start adapter implementation plan
r1tsuu Apr 1, 2026
df1da07
refactor(ui): move shared utilities from packages/next to packages/ui
r1tsuu Apr 1, 2026
f1cd757
refactor(ui): move Nav and DocumentHeader elements from packages/next
r1tsuu Apr 1, 2026
2acb87e
refactor(ui): move Default and Minimal templates from packages/next
r1tsuu Apr 1, 2026
0bdb555
refactor(ui): move Document view utilities from packages/next
r1tsuu Apr 1, 2026
fbf3c82
refactor(ui): move List view utilities from packages/next
r1tsuu Apr 1, 2026
a6fb859
refactor(ui): move Version/Versions/Account utilities from packages/next
r1tsuu Apr 1, 2026
2052471
refactor(ui): move renderDocument and renderListView to packages/ui w…
r1tsuu Apr 1, 2026
bf649fd
refactor(ui): move Version/Versions/Account render functions to packa…
r1tsuu Apr 1, 2026
14af6d3
refactor(ui): move renderDocumentHandler/renderListHandler and create…
r1tsuu Apr 1, 2026
c7bd3cb
feat(tanstack-start): implement full adapter with vinxi/http cookies …
r1tsuu Apr 1, 2026
6310492
refactor(ui): move Root view utilities and create RenderRoot.tsx
r1tsuu Apr 1, 2026
022409b
feat(tanstack-start): add Phase 6 app shell and dev:tanstack command
r1tsuu Apr 1, 2026
1aaf4e5
feat(tanstack-start): fix Phase 6 for @tanstack/react-start@1.167 API
r1tsuu Apr 1, 2026
a2a11dd
chore(tanstack-start): update peer deps to @tanstack/react-start, add…
r1tsuu Apr 1, 2026
984c814
fix(tanstack-start): dashboard render and virtual module compat patches
r1tsuu Apr 1, 2026
b0c2fc8
feat(tanstack-start): Phase 6 e2e improvements
r1tsuu Apr 1, 2026
1d34948
docs: add TanStack Start client rendering design
r1tsuu Apr 2, 2026
3f689c8
docs: add TanStack Start client rendering implementation plan
r1tsuu Apr 2, 2026
790185b
chore(tanstack-start): update client rendering plan — add Phase 0 bui…
r1tsuu Apr 2, 2026
76df99d
docs: rewrite tanstack start client rendering plan
r1tsuu Apr 2, 2026
1a3f67c
fix(test): wire tanstack suite selection into dev and e2e
r1tsuu Apr 2, 2026
110071c
feat(tanstack-start): add serializable admin page rendering
r1tsuu Apr 2, 2026
05fbe55
fix: keep tanstack sass suppression and dynamic import guard
r1tsuu Apr 2, 2026
ec99886
docs: add tanstack server-client boundary rework plan
r1tsuu Apr 2, 2026
d89e0c9
docs: add ui framework-agnostic views plan
r1tsuu Apr 2, 2026
c21111a
refactor(ui): introduce shared view renderer boundary
r1tsuu Apr 2, 2026
1a8841a
refactor(ui): extend shared renderer through admin views
r1tsuu Apr 2, 2026
fc9cde8
docs: extend ui framework-agnostic views plan
r1tsuu Apr 2, 2026
625aa17
chore: collapse tanstack admin page onto shared ui views
r1tsuu Apr 2, 2026
8e291a1
docs: refine root-first ui framework-agnostic views plan
r1tsuu Apr 2, 2026
e6449e6
chore: extract shared root bootstrap and default shell
r1tsuu Apr 2, 2026
fdbefb0
docs: expand framework-agnostic view rollout
r1tsuu Apr 2, 2026
f675fbe
docs: clarify shared server logic boundary
r1tsuu Apr 2, 2026
3efa557
chore: split tanstack auth views out of admin page
r1tsuu Apr 2, 2026
f2ce6e4
docs: extend shared view reuse guidance
r1tsuu Apr 2, 2026
40e29eb
docs: tighten dashboard reuse direction
r1tsuu Apr 2, 2026
3a1460f
docs: codify dashboard as reference pattern for all views
r1tsuu Apr 2, 2026
c839643
feat: reuse shared ModularDashboardClient across Next and TanStack
r1tsuu Apr 2, 2026
554d0bf
refactor: remove re-export wrapper for ModularDashboard in Next adapter
r1tsuu Apr 2, 2026
77fd2d9
refactor: streamline TanStackAdminPage by removing unused components …
r1tsuu Apr 2, 2026
7826d6f
fix(ui): resolve SCSS imports and TS errors breaking build
r1tsuu Apr 2, 2026
91ba9d5
feat: add folder view adapters and architecture doc
r1tsuu Apr 2, 2026
e6f05f5
refactor: enhance Dashboard view architecture and exports
r1tsuu Apr 2, 2026
5efa8f4
feat: data-first view loading for TanStack adapters
r1tsuu Apr 2, 2026
b20504c
docs: update architecture doc with data-first view loading pattern
r1tsuu Apr 2, 2026
c578cdb
chore(tanstack-start): fix prefetch attr, add exports, loader-based r…
r1tsuu Apr 3, 2026
b0ca0a2
docs: add serializable server functions implementation plan
r1tsuu Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
839 changes: 839 additions & 0 deletions docs/plans/2026-03-31-admin-adapter-combined.md

Large diffs are not rendered by default.

1,516 changes: 1,516 additions & 0 deletions docs/plans/2026-04-01-tanstack-start-adapter.md

Large diffs are not rendered by default.

219 changes: 219 additions & 0 deletions docs/plans/2026-04-02-data-first-view-loading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Data-First View Loading

## Goal

Replace the fragile `useEffect`-based server function pattern in TanStack view adapters with loader-based data fetching, and extract shared data-fetching functions so both Next RSC and TanStack loaders can fetch data before rendering.

## The Problem

The current TanStack adapters for List, Document, BrowseByFolder, and CollectionFolders all follow this pattern:

```mermaid
sequenceDiagram
participant Loader as TanStack Loader
participant Comp as Client Component
participant SF as Server Function

Loader->>Comp: pageState (metadata only)
Note over Comp: Renders LoadingOverlay
Comp->>SF: useEffect → serverFunction("render-list")
SF-->>Comp: { List: React.ReactNode }
Note over Comp: setState → re-render with JSX
```

This is unreliable:

- **Waterfall**: loader fetches metadata, component mounts, THEN fetches actual view content
- **Flash of loading**: user always sees `LoadingOverlay` before content
- **Race conditions**: fast navigation can cause stale state updates (mitigated by `cancelled` flag, but fragile)
- **Serialization concern**: shipping pre-rendered `React.ReactNode` over a server function POST is opaque and hard to debug

The Dashboard already works correctly — `getPageState` fetches dashboard data in the loader, and the component renders immediately with data. Auth views work the same way. List/Document/Folder views should follow the same pattern.

## Target Architecture

```mermaid
sequenceDiagram
participant Loader as TanStack Loader / Next RSC
participant DataFn as Shared Data Function
participant Comp as Shared UI Component

Loader->>DataFn: fetchListData(req, collection, query)
DataFn-->>Loader: { docs, columns, permissions, ... }
Loader->>Comp: Pass data as props
Note over Comp: Renders immediately — no loading state
```

Both frameworks call the same shared data-fetching function, then pass data to the same shared component:

- **Next**: RSC calls `fetchListData()` → passes data to `<ListView data={...} />`
- **TanStack**: Loader calls `fetchListData()` → serializes as JSON → component receives via `useLoaderData()` → passes to same `<ListView data={...} />`

The shared component is a regular React client component. No async, no server function, no useEffect for initial data.

## Key Constraint: Serializable Data, Not JSX

The current builders (`renderListView`, `renderDocument`) return `React.ReactNode` — pre-rendered JSX that includes providers, slot components, tables, etc. This cannot be JSON-serialized through a TanStack loader.

The fix is to split each builder into two layers:

1. **Data function** (shared, framework-agnostic) — fetches and returns plain serializable data
2. **Render function** (shared, React component) — accepts data props, renders JSX

The existing builders can be refactored to compose these two layers internally, so the `renderListView` API doesn't break.

## Phased Approach

### Phase 0: Inventory what each view needs as data

Before coding, identify the minimum serializable data each view needs. This determines the shape of the new data functions.

**List view** (from `renderListView` internals):

- `docs: PaginatedDocs` — the query result
- `columnState: Column[]` — resolved column definitions
- `hasCreatePermission, hasDeletePermission, hasTrashPermission: boolean`
- `newDocumentURL: string`
- `collectionSlug: string`
- `queryPreset, resolvedFilterOptions` — filter/preset data
- Collection preferences

**Document view** (from `renderDocument` internals):

- `doc: Data` — the document record (already returned as `data`)
- `formState: FormState` — serialized field state
- `docPermissions: SanitizedDocumentPermissions`
- `docPreferences`
- `isLocked: boolean`
- `versions` data
- Collection/global config reference

**Folder views**: Similar to List — paginated results + folder hierarchy data.

### Phase 1: Extract data-fetching functions from builders

For each view, extract a `fetchXxxViewData()` function from the existing builder. These functions take `req` + view-specific args and return plain serializable data.

**Files to create/modify:**

- `packages/ui/src/views/List/fetchListViewData.ts` — new, extracted from `packages/ui/src/views/List/RenderListView.tsx` lines 81-350 (the data-fetching portion before JSX assembly)
- `packages/ui/src/views/Document/fetchDocumentViewData.ts` — new, extracted from `packages/ui/src/views/Document/RenderDocument.tsx`
- `packages/ui/src/views/BrowseByFolder/fetchBrowseFolderData.ts` — new
- `packages/ui/src/views/CollectionFolders/fetchCollectionFolderData.ts` — new

Each returns a typed, JSON-serializable data object. No React nodes.

Refactor the existing builders to call these functions internally:

```ts
renderListView(args) {
const data = await fetchListViewData(args) // extracted
return { List: <DefaultListView {...data} /> } // existing JSX assembly
}
```

This is a non-breaking refactor — the existing `renderListView` API and Next adapters continue to work unchanged.

### Phase 2: Create shared client view components that accept data props

For each view, create (or adapt) a client component that accepts the data object from Phase 1 and renders the view.

**Key files:**

- The List view's `DefaultListView` (`packages/ui/src/views/List/index.tsx`) already accepts `ListViewClientProps` but also expects pre-built JSX in slots (`Table`, `renderedFilters`). The Table and filters would need to be built client-side from data.
- The Document view's `EditView` and `DocumentInfoProvider` already handle most rendering from data props. The gap is `formState` and slot resolution.
- The Dashboard's `ModularDashboardClient` already works this way — it is the reference.

This is the hardest phase. The main challenge is that some props are currently `React.ReactNode` (pre-rendered server-side):

- `Table` in ListViewSlots
- `renderedFilters` in List
- Document slots (Save, Publish, Preview buttons)

These would need to move to client-side rendering from data/config, or use server functions for on-demand rendering (like `RenderWidget` already does for dashboard widgets).

### Phase 3: Move data fetching into TanStack loader via `getPageState`

Extend `packages/tanstack-start/src/views/Root/getPageState.ts` to call the new data functions for each view type, just like it already does for `dashboard`:

```ts
// In getPageState, after determining viewType:
if (pageViewType === 'list') {
pageData = {
...pageData,
list: await fetchListViewData({ req, collectionSlug, query, ... }),
}
}

if (pageViewType === 'document') {
pageData = {
...pageData,
document: await fetchDocumentViewData({ req, collectionSlug, docID, ... }),
}
}
```

Since `getPageState` runs in the TanStack loader, data is available immediately when the component mounts — no useEffect, no loading spinner, no waterfall.

### Phase 4: Rewrite TanStack view adapters to use loader data

Replace the `useEffect` + `serverFunction` pattern with direct data consumption:

```tsx
// Before (current):
function ListView({ pageState }) {
const [listView, setListView] = useState(null)
useEffect(() => {
serverFunction({ name: 'render-list', args: ... })
.then(result => setListView(result.List))
}, [])
return isLoading ? <LoadingOverlay /> : listView
}

// After (target):
function ListView({ pageState }) {
const listData = pageState.pageData.list
return <SharedListView {...listData} />
}
```

**Files to modify:**

- `packages/tanstack-start/src/views/List/index.tsx`
- `packages/tanstack-start/src/views/Document/index.tsx`
- `packages/tanstack-start/src/views/BrowseByFolder/index.tsx`
- `packages/tanstack-start/src/views/CollectionFolders/index.tsx`
- `packages/tanstack-start/src/views/Account/index.tsx`
- `packages/tanstack-start/src/views/Root/types.ts` — extend `SerializablePageData` with list/document/folder data types

### Phase 5: Align Next adapters to use the same data functions

Next adapters can optionally be refactored to call the same `fetchXxxViewData()` functions, making the data-fetching truly shared:

```tsx
// packages/next/src/views/List/index.tsx
export const ListView = async (args) => {
const data = await fetchListViewData(args)
return <SharedListView {...data} />
}
```

This is optional for Phase 1 since Next's current RSC pattern already works. But it aligns the architecture: both frameworks call the same data function, pass to the same component.

## Recommended Order

1. **Start with List** — it's the most commonly used view after Dashboard and has well-understood data needs
2. **Then Document** — more complex due to form state, but high impact
3. **Then Folder views** — similar to List
4. **Account last** — smallest scope

## The Hard Part: Table and Slots as JSX

The biggest obstacle is that `renderListView` currently builds `Table` as pre-rendered JSX server-side (with `renderTable()`). The table includes resolved cell components from the import map.

Two approaches:

- **Option A**: Render table client-side from column definitions + data. The `Table` component would need to resolve cell renderers client-side. This is the cleaner architecture but more work.
- **Option B**: Keep using `RenderWidget`-style server functions for table/slot rendering on demand. The initial data (docs, columns, permissions) loads in the loader, but the table body fetches via server function after mount. This is a hybrid — eliminates the main waterfall while deferring heavy JSX resolution.

The Dashboard already uses Option B for widgets (`RenderWidget` calls `render-widget` server function). This is a proven pattern in the codebase.
Loading