Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 38 additions & 0 deletions .changeset/20050-view-page-size-default-50.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
'@objectstack/spec': minor
---

feat(spec): a view that declares no page size now shows 50 per page — `PaginationConfigSchema.pageSize` defaults to 50 (was 25)

The platform display page size is 50 (maintainer ruling on objectui#9853,
「9853 默认页大小改为50」). It is declared in one place, the view contract's
`PaginationConfigSchema.pageSize`, and the renderer reads that spec default
rather than keeping a number of its own — so the change lands in the protocol
first.

**What changes for an author who omits the page size:**

- A view whose `pagination` block does not set `pageSize` now parses to
`pageSize: 50` where it parsed to `25`: 50 rows per page on a paged view, and
a fetch ceiling of 50 on a view with no pager (kanban, gallery, timeline).
- A view with no `pagination` block at all parses with none, before and after;
its page size comes from the renderer, which takes this spec default (the
Console does so once objectui#9853 lands).

**What does not change:** the accept set. `pageSize` is still a positive
integer; `0`, negatives and fractions are refused exactly as before, and every
page size an author wrote parses to the number they wrote.

### Migration: FROM → TO

| FROM | TO |
| :--- | :--- |
| a view that declares no page size and relied on 25 rows per page | write it: `pagination: { pageSize: 25 }` |
| a view that declares no page size and should follow the platform default | change nothing — it now shows 50 |
| reading `PaginationConfigParsed.pageSize` after parsing a `pagination` block without `pageSize` | it yields `50` where it yielded `25`; the type is unchanged |
| reading the published JSON Schema's `default` for `ui/PaginationConfig` `pageSize` | it is `50` |

The move is declared in `DEFAULT_CHANGES_BY_MAJOR`
(`packages/spec/scripts/lib/default-changes.ts`, `ui/PaginationConfig:pageSize`
25 → 50) and carried on the upgrade path as the semantic entry
`view-pagination-page-size-default-50`.
6 changes: 3 additions & 3 deletions content/docs/references/ui/view.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,7 @@ View filter rule

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **pageSize** | `integer` | optional (default: `25`) | Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set |
| **pageSize** | `integer` | optional (default: `50`) | Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set |
| **pageSizeOptions** | `integer[]` | optional | Available page size options |

### Nested Shape: `ListView.kanban`
Expand Down Expand Up @@ -1326,7 +1326,7 @@ View filter rule

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **pageSize** | `integer` | optional (default: `25`) | Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set |
| **pageSize** | `integer` | optional (default: `50`) | Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set |
| **pageSizeOptions** | `integer[]` | optional | Available page size options |

### Nested Shape: `ObjectListView.kanban`
Expand Down Expand Up @@ -1595,7 +1595,7 @@ Quick-filter field configuration

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **pageSize** | `integer` | optional (default: `25`) | Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set |
| **pageSize** | `integer` | optional (default: `50`) | Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered set is larger than it, show a visible truncation signal saying what is on screen is not the whole set |
| **pageSizeOptions** | `integer[]` | optional | Available page size options |


Expand Down
2 changes: 1 addition & 1 deletion packages/spec/authorable-defaults/ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"ui/PageTabsProps:position = \"top\"",
"ui/PageTabsProps:tabStyle = \"line\"",
"ui/PageVariable:type = \"string\"",
"ui/PaginationConfig:pageSize = 25",
"ui/PaginationConfig:pageSize = 50",
"ui/RecordActivityProps:enableMentions = true",
"ui/RecordActivityProps:enableReactions = false",
"ui/RecordActivityProps:enableThreading = false",
Expand Down
21 changes: 21 additions & 0 deletions packages/spec/scripts/lib/default-changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,5 +533,26 @@ export const DEFAULT_CHANGES_BY_MAJOR: Readonly<Record<number, readonly Declared
+ '`required` that lists defaulted keys is this repo\'s existing output-mode convention, not '
+ 'a new one.',
},
{
key: 'ui/PaginationConfig:pageSize',
from: '25',
to: '50',
reason:
'A RULED behaviour change, not a correction: the platform display page size is 50 '
+ '(maintainer ruling on objectui#9853, verbatim 「9853 默认页大小改为50」), and the spec '
+ 'declaration is where that number lives — the renderer is ruled to read the spec default '
+ 'rather than keep a page size of its own (objectui#9853 ruling C′ item 1), so the change '
+ 'lands here first. '
+ 'What moves: a view whose `pagination` block omits `pageSize` now parses to `pageSize: 50` '
+ 'where it parsed to `25` — 50 rows per page on a paged view, and a fetch ceiling of 50 on a '
+ 'view with no pager (kanban, gallery, timeline). A view with NO `pagination` block parses '
+ 'with none on either side, so the parse materialises nothing there; the number reaches it '
+ 'only through a renderer that takes the spec default. The accept set does not move: '
+ '`pageSize` is still a positive integer, and `0`, negatives and fractions are refused '
+ 'exactly as before. A `pageSize` written into a document — by its author, or by a tool '
+ 'that serialised an earlier parse — is an authored value and keeps its number. '
+ 'To keep 25 rows per page, write it: `pagination: { pageSize: 25 }`. To take the platform '
+ 'default, change nothing.',
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import type { SemanticMigration } from '../../types.js';

// The display page size a view gets when it declares none moved from 25 to 50
// (maintainer ruling on objectui#9853). A default move reaches every silent
// document with no parse error and nothing in the author's diff, so the
// upgrade path carries it as a TODO: only the deployment can say whether a
// view that never declared a page size was relying on 25.
export const entry: SemanticMigration = {
id: 'view-pagination-page-size-default-50',
// No backticks in `surface` — build-upgrade-guide.ts renders it inside a code
// span AND a table cell.
surface: 'ui.PaginationConfig.pageSize — an OMITTED page size on a view',
replacement: 'nothing, to take the platform display page size of 50. To keep the old 25 rows '
+ 'per page on a view, write it: `pagination: { pageSize: 25 }`',
reason:
'A RULED behaviour change on a default, so there is nothing to rewrite and nothing to '
+ 'refuse: the maintainer set the platform display page size to 50 (「9853 默认页大小改为50」, '
+ 'objectui#9853), and the declared default of `PaginationConfigSchema.pageSize` moved '
+ 'from 25 to 50. A `pagination` block that omits `pageSize` now parses to 50 — 50 rows '
+ 'per page on a paged view, and a fetch ceiling of 50 on a view with no pager (kanban, '
+ 'gallery, timeline). A view with no `pagination` block at all parses with none on either '
+ 'side; its page size reaches it through the renderer, which is ruled to read the spec '
+ 'default rather than keep its own number (objectui#9853 ruling C′ item 1). Not losslessly '
+ 'convertible because the question is intent, not text: a mechanical pass that wrote '
+ '`pageSize: 25` into every silent view would preserve the old number and defeat the '
+ 'ruling, and one that wrote 50 would add nothing the default does not already do. Only '
+ 'the deployment knows which silent views were relying on 25. The accept set is unchanged '
+ '— a positive integer — and every authored `pageSize` parses exactly as before.',
acceptanceCriteria:
'An empty pagination configuration parses to a page size of 50, and a list view carrying '
+ '`pagination: {}` parses to `pagination.pageSize` 50; an authored '
+ '`pagination: { pageSize: 25 }` still parses to 25; `pageSize: 0`, a negative and a '
+ 'fraction are still refused. A view that must keep 25 rows per page declares '
+ '`pagination: { pageSize: 25 }` and shows 25 rows on its first page.',
};
33 changes: 33 additions & 0 deletions packages/spec/src/migrations/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14053,6 +14053,39 @@ const step18: MigrationStep = {
+ 'equals, and operator: "in" with value: ["won"] — select the same rows, so the result '
+ 'set cannot tell you which the metadata meant, and only the author knows.',
},
// The display page size a view gets when it declares none moved from 25 to 50
// (maintainer ruling on objectui#9853). A default move reaches every silent
// document with no parse error and nothing in the author's diff, so the
// upgrade path carries it as a TODO: only the deployment can say whether a
// view that never declared a page size was relying on 25.
{
id: 'view-pagination-page-size-default-50',
// No backticks in `surface` — build-upgrade-guide.ts renders it inside a code
// span AND a table cell.
surface: 'ui.PaginationConfig.pageSize — an OMITTED page size on a view',
replacement: 'nothing, to take the platform display page size of 50. To keep the old 25 rows '
+ 'per page on a view, write it: `pagination: { pageSize: 25 }`',
reason:
'A RULED behaviour change on a default, so there is nothing to rewrite and nothing to '
+ 'refuse: the maintainer set the platform display page size to 50 (「9853 默认页大小改为50」, '
+ 'objectui#9853), and the declared default of `PaginationConfigSchema.pageSize` moved '
+ 'from 25 to 50. A `pagination` block that omits `pageSize` now parses to 50 — 50 rows '
+ 'per page on a paged view, and a fetch ceiling of 50 on a view with no pager (kanban, '
+ 'gallery, timeline). A view with no `pagination` block at all parses with none on either '
+ 'side; its page size reaches it through the renderer, which is ruled to read the spec '
+ 'default rather than keep its own number (objectui#9853 ruling C′ item 1). Not losslessly '
+ 'convertible because the question is intent, not text: a mechanical pass that wrote '
+ '`pageSize: 25` into every silent view would preserve the old number and defeat the '
+ 'ruling, and one that wrote 50 would add nothing the default does not already do. Only '
+ 'the deployment knows which silent views were relying on 25. The accept set is unchanged '
+ '— a positive integer — and every authored `pageSize` parses exactly as before.',
acceptanceCriteria:
'An empty pagination configuration parses to a page size of 50, and a list view carrying '
+ '`pagination: {}` parses to `pagination.pageSize` 50; an authored '
+ '`pagination: { pageSize: 25 }` still parses to 25; `pageSize: 0`, a negative and a '
+ 'fraction are still refused. A view that must keep 25 rows per page declares '
+ '`pagination: { pageSize: 25 }` and shows 25 rows on its first page.',
},
{
id: 'wait-node-event-config-required',
surface:
Expand Down
11 changes: 7 additions & 4 deletions packages/spec/src/ui/view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1434,20 +1434,23 @@ describe('SelectionConfigSchema', () => {
});

describe('PaginationConfigSchema', () => {
it('should default pageSize to 25', () => {
it('should default pageSize to 50', () => {
const pagination = {};

const result = PaginationConfigSchema.parse(pagination);
expect(result.pageSize).toBe(25);
expect(result.pageSize).toBe(50);
});

it('should accept custom page size', () => {
// 25 — the pre-#20050 default, which an author who wants it back now
// writes explicitly. A value OTHER than the default, so this pin still
// discriminates "the authored value survives" from "the default applied".
const pagination = {
pageSize: 50,
pageSize: 25,
};

const result = PaginationConfigSchema.parse(pagination);
expect(result.pageSize).toBe(50);
expect(result.pageSize).toBe(25);
});

it('should accept page size options', () => {
Expand Down
7 changes: 6 additions & 1 deletion packages/spec/src/ui/view.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1096,12 +1096,17 @@ export const SelectionConfigSchema = lazySchema(() => strictObject({

/**
* List View Pagination Configuration
*
* `pageSize` defaults to 50 — the platform's display page size, set by the
* maintainer's ruling on objectui#9853 (「9853 默认页大小改为50」). This
* declaration is the one place that number lives: a renderer reads the spec
* default, it never keeps a page size of its own.
*/
export const PaginationConfigSchema = lazySchema(() => strictObject({
surface: 'this pagination configuration',
history: VIEW_HISTORY,
}, {
pageSize: z.number().int().positive().default(25).describe(
pageSize: z.number().int().positive().default(50).describe(
'Number of records per page. On a view with no pager (kanban, gallery, timeline) it is the fetch '
+ 'ceiling, and the renderer owes two things: bound its fetch at this number, and, when the filtered '
+ 'set is larger than it, show a visible truncation signal saying what is on screen is not the whole set',
Expand Down
Loading