Skip to content

Add pagination example#333

Open
nikitamelni wants to merge 11 commits into
mainfrom
add-pagination-example
Open

Add pagination example#333
nikitamelni wants to merge 11 commits into
mainfrom
add-pagination-example

Conversation

@nikitamelni

Copy link
Copy Markdown
Contributor

No description provided.

@netlify

netlify Bot commented May 28, 2026

Copy link
Copy Markdown

Deploy Preview for context-edge canceled.

Name Link
🔨 Latest commit 72b7ce2
🔍 Latest deploy log https://app.netlify.com/projects/context-edge/deploys/6a1dcd24e32aa9000899186f

@vercel

vercel Bot commented May 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
context-edge-vercel Ready Ready Preview, Comment Jun 1, 2026 6:20pm
hello-world-nextjs-approuter Ready Ready Preview, Comment Jun 1, 2026 6:20pm
nextjs-hello-world-protected Ready Ready Preview, Comment Jun 1, 2026 6:20pm
svelte-demo Ready Ready Preview, Comment Jun 1, 2026 6:20pm
sveltekitunfrm Ready Ready Preview, Comment Jun 1, 2026 6:20pm
uniform-hello-world Ready Ready Preview, Comment Jun 1, 2026 6:20pm
uniform-mesh-team-ai-translation Ready Ready Preview, Comment Jun 1, 2026 6:20pm
uniform-smarting-demo-fr-webhooks Ready Ready Preview, Comment Jun 1, 2026 6:20pm
uniform-smarting-demo-webhooks Ready Ready Preview, Comment Jun 1, 2026 6:20pm

Request Review

nikitamelni and others added 11 commits June 1, 2026 14:19
Bare-bone copy of the App Router starter to host two pagination
demonstrations in separate follow-up commits. README describes the
tradeoffs and the two demo routes; no behavioural changes yet.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Server component reads `limit` from `context.dynamicInputs`, slices its
source data, and only the visible window is rendered. A client "Load
more" button does router.replace('?limit=…', { scroll: false }) inside
useTransition for soft-navigation re-render with no page reload.

The corresponding Uniform composition '01 - Datasource Pagination' is
already authored at /:locale/pagination-datasource, with `limit` declared
as an allowed query string on the project map node (default 5). That
declaration is what lets the value survive middleware buildRoutePath and
reach the Route API as a dynamic input.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Client component `PaginationContainer` uses `getUniformSlot({ slot })` to
read its `cards` slot as an array, slices it with a useState counter, and
exposes a Load more button. Card is a small server component that renders
title/description with UniformText.

Adapted from the pages-router `wrapperComponent={({ items }) => …}` pattern —
the App Router SDK's UniformSlot is per-child, so the array-style slice
lives in the component body via getUniformSlot.

The corresponding Uniform composition '02 - Pagination Container' is
already authored at /:locale/pagination-slot with 11 card instances.

Tradeoff (documented in README): because the container is a client
component, all slot children get serialized into the RSC payload up front.
Load more is instant and there's no server round-trip, but the unrevealed
items are still shipped. Pick this when the slot is small/bounded; pick
approach #1 when the tail is large enough to matter.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The starter's `rewriteRequestPath` was passing only `url.pathname`, so
`?limit=10` was being stripped before the SDK ever saw it. The Route API
then returned the project map node's default `limit=5`, and the page
appeared frozen at 5 items.

Verified end-to-end: with the path including `url.search`, the Route API
returns `dynamicInputs: { limit: "<url value>", locale: "en" }`. The
project map node already had `data.queryStrings: [{ name: "limit",
value: "5" }]` correctly stored by MCP — the bug was purely in this
example's middleware glue.

package-lock.json is also updated to reflect the package rename from
the scaffolding commit (this happens automatically on `npm install`).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switch PaginationContainer from cumulative reveal to page-at-a-time:
- `defaultLimit` is now the page size; only items in the current page
  window are visible, and moving forward replaces them (older items
  disappear from view, no growing list).
- Replace single Load more button with Prev / Next + page indicator.
- `loadMoreText` is repurposed as the Next-button label (set to
  "Next →" on the composition in Uniform via MCP).
- `step` parameter kept in the type for backwards compatibility but is
  unused in page mode.

README rewritten for both approaches:
- Approach #1: callout about the middleware `url.search` requirement so
  others don't trip on the same issue.
- Approach #2: copy reflects page-at-a-time behaviour (not append),
  with the tradeoff note still calling out that all cards are shipped
  upfront because PaginationContainer is a client component.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Match Approach #2's UX so the two demos differ only in *where* the
slicing happens, not in how the user navigates.

- Project map node updated via Canvas API: `data.queryStrings` is now
  `[{ name: "page", value: "1" }]` (was `limit`).
- `paginatedList.tsx` reads `context.dynamicInputs.page`, computes the
  slice `[(page-1)*PAGE_SIZE, page*PAGE_SIZE)` from 47 items, and renders
  only that page. Page size is a constant in code.
- `loadMore.tsx` is replaced by `paginationControls.tsx` — Prev / Next
  buttons that call `router.replace('?page=N', { scroll: false })` inside
  `useTransition`, with disabled states at the ends.
- README rewritten for the new flow; gotcha callout about `url.search`
  in middleware kept.

Verified against the Route API: `?page=3` → `dynamicInputs.page="3"`;
no query → default `page="1"`. Each distinct `?page=N` is a separate
cache entry in the Route fetch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…iformSlot

The composition has been restructured in Uniform to a dynamic project map
node `/:locale/pagination-datasource/:offset`, with a $loop in the
paginatedList's `cards` slot bound to a `Query Blog Entry Content`
data resource (queryBlogEntry data type) whose `offset` variable picks
up the dynamic input and `limit` is fixed to 5. The Route API now
returns exactly the 5-item window per page.

Verified against the Route API:
- offset=0 → 5 cards (Demo Blog Post 1, 10, 2, 3, 4)
- offset=5 → 5 cards (Demo Blog Post 5..9)
- offset=8 → 2 cards  (partial last page)
- offset=10 → 0 cards (past the end)

Code changes:
- paginatedList.tsx: drop the 47-item SOURCE_DATA constant and the
  slicing logic. Render `slots.cards` directly via UniformSlot. Read
  `context.dynamicInputs.offset` only to derive the current page for
  the controls.
- paginationControls.tsx: switch from `?page=N` query strings to a path
  segment — `router.replace(`${basePath}/${page}`, { scroll: false })`.
  Next is disabled when the partial slot tells us we're on the last page.
- lib/paginationDatasource.ts (new): shared `PAGE_SIZE = 5`,
  `rewritePaginationDatasourcePath()`, and `offsetToPage()` helpers so
  the middleware and the component can't drift.
- middleware.ts: middleware's `rewriteRequestPath` now translates the
  visitor-facing path `/pagination-datasource/<page>` into the
  offset-based path Uniform expects, before forwarding to the SDK.

The only computation in TypeScript is `(page - 1) * PAGE_SIZE` and its
inverse — all data shaping happens in Uniform.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…demos

Two follow-ups while wiring up Approach #1's data resource:

1. Empty page → page 1. `rewritePaginationDatasourcePath` now also
   rewrites `/en/pagination-datasource` and `/en/pagination-datasource/`
   to `/en/pagination-datasource/0`, so a bare URL renders the first
   page instead of 404-ing on the `:offset` project map node. The
   marker-match logic also guards against accidental matches like
   `/pagination-datasource-other/...`.

2. Shared Prev / Next UI. `paginationControls.tsx` is now a small,
   presentational client component (`hasPrev`, `hasNext`, `onPrev`,
   `onNext`, `pending`, indicator via `children`). Both demos use it:
   - Approach #1 wraps it in `routerPagination.tsx`, which holds
     `useRouter` + `useTransition` and turns the callbacks into soft
     navigations. Indicator is `Page {currentPage}`.
   - Approach #2 (`paginationContainer.tsx`) calls `PaginationControls`
     directly and wires the callbacks to `useState`. Indicator is
     `Page {n} of {total}`. The unused `loadMoreText` UniformText is
     dropped — both buttons now use fixed labels for visual parity.

Smoke-tested the rewriter against bare paths, page numbers, trailing
slashes, anchors, and a near-miss prefix (`-extra/3`) — all expected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Make the Prev / Next button labels configurable from Uniform (per locale,
with inline-edit support via UniformText) instead of hardcoding them in
the React tree, and remove the back-compat parameters that no longer do
anything.

Uniform side (via MCP):
- paginatedList component: added `previousLabel`, `nextLabel` text params
  (localizable). Composition #1 sets them to "← Previous" / "Next →".
- paginationContainer component: removed `step` and `loadMoreText`,
  added `previousLabel`, `nextLabel`. Composition #2 sets them likewise.
  Stale `step` / `loadMoreText` values on the existing composition
  instance were also cleared (MCP couldn't reset values for params that
  no longer exist on the definition — fell back to one Canvas API PUT
  for that single cleanup, then verified via MCP).
- titleParameter on paginationContainer switched from `loadMoreText` to
  `nextLabel` so the canvas tree still shows a sensible instance title.

Code side:
- PaginationControls: drop the fixed "← Previous" / "Next →" strings.
  New required `previousLabel` / `nextLabel` ReactNode props are rendered
  inside each button.
- routerPagination + paginatedList + paginationContainer: pass
  `<UniformText component={component} parameter={label} as="span" />`
  for each label, with a string fallback if the parameter is absent.
- paginationContainer.tsx: drop the `step` and `loadMoreText` props from
  the type as well as the unused branches that consumed them. The
  component is now strictly a paged slot wrapper.

Composition #2 also now uses the same `Query Blog Entry Content` data
resource as #1 (window = 50, no offset), so the two demos differ only in
*where* the slicing happens — README updated to reflect this.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pulled via `npm run uniform:pull` after the Approach #1 / #2 build-out:

- New component definitions: paginatedList, paginationContainer, card
- Updated page component (cards / paginationContainer added to content
  slot allowedComponents)
- Two compositions (`01 - Datasource Pagination`, `02 - Pagination
  Container`)
- Dynamic project map nodes: `/:locale/pagination-datasource/:offset`
  (composition #1) and `/:locale/pagination-slot` (composition #2)
- Data types backing the blog content: blogEntry, blogEntryList,
  queryBlogEntry; `helloWorld` removed from the project
- Blog `contentType` plus 10 demo `entry` records that feed the data
  resource both demos paginate through

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A customer cloning this example needs to push the committed
`uniform-data/` into their own (potentially empty) Uniform project before
running the dev server. The previous Getting Started only mentioned
`uniform:pull`, which only makes sense after the content is already in
the project.

New flow: prepare project → env → install → push → manifest → dev.
`uniform:pull` is still mentioned, as a follow-up tip for syncing UI
edits back into git.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants