Skip to content

Commit a4ca69a

Browse files
fix(spec): one row bound per view — retire the unpublished per-kind view limit (#19809)
Part of #19228 Clause-②: no Rewritten short by the dispatching seat (2026-09-23T08:08Z). The developer's measurements are in its report on #19228; the at-tier review record is `5791323483` on this PR. ## What changes Ruling D (#19228, `5789634193`): a view carries one row bound, `pagination.pageSize`. The per-kind view `limit` added for #17393 was never published (npm `latest` is 17.4.0; the key is absent from its tarball), so it is removed before a release carries it. - `GalleryConfigSchema` / `KanbanConfigSchema` / `TimelineConfigSchema` lose `limit`; `rowLimitKey`, `DEFAULT_VIEW_ROW_LIMIT` and `KanbanConfigParsed` go with it (Kanban has one shape again, re-pinned per ADR-0122). - A written `limit` in those blocks is refused as an unknown key, and the refusal names both alternatives: `pagination.pageSize` on a view, or the flat `limit` on a page component node. - `pagination.pageSize`'s description carries the truncation obligation that `limit`'s description carried. - The `object-kanban` / `object-timeline` component `limit` keys and their precedence are unchanged; only text describing the removed view-level key was deleted. - The pending release note `.changeset/17393-view-row-ceiling.md` is deleted, and the pending `.changeset/19228-view-row-limit-route-record.md` loses its section about the view-level `limit`, so no release announces a key that does not ship. A `patch` changeset covers the description change. Net: 16 files, +86 / −528. ## ⚠️ `Check Changeset` is red on purpose This PR corrects two PENDING release notes instead of adding one, which `check-empty-changeset.mjs` names the DELIBERATE CORRECTION class. The workflow (`pr-automation.yml`, route 0) says to leave the check red and get the correction confirmed in writing on the PR; `Check Changeset` is not a required context. The two notes and what changed under them are listed above. **Maintainer confirmation requested on this PR.** ## Not in this PR objectui still spreads a view's kanban / gallery / timeline block onto the rendered node; reading `pagination.pageSize` as the fetch ceiling and showing the truncation signal there is objectui work. objectui#7390 is closed, so that work has no open card; triage is asked to route it (#19228). ## 维护者速读 - 看板、画廊、时间线视图原来各有一个「最多显示多少条」的 `limit`,从没发布过;按您的裁决 D,改为每个视图只用 `pagination.pageSize` 一个上限。 - 两条**尚未发布**的更新说明被删 / 删减,免得下次发版宣布一个不存在的设置;`Check Changeset` 因此按规定保持红色,**需要您在本 PR 上书面确认一句**。 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2491729 commit a4ca69a

16 files changed

Lines changed: 86 additions & 528 deletions

File tree

‎.changeset/17393-view-row-ceiling.md‎

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
`pagination.pageSize` states what the renderer owes on a view with no pager
6+
7+
On a kanban, gallery or timeline view there is no pager, so `pagination.pageSize` is the fetch
8+
ceiling. Its description now says so, and names the renderer's two obligations there: bound the
9+
fetch at that number, and, when the filtered set is larger than it, show a visible truncation
10+
signal saying what is on screen is not the whole set.
11+
12+
The key's accept set and its default (`25`) are unchanged, and no export or authorable key moves
13+
relative to the last published release.
14+
15+
Clause-②: no

‎.changeset/19228-view-row-limit-route-record.md‎

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22
"@objectstack/spec": patch
33
---
44

5-
fix(spec): state the row-cap guard `ElementDataSourceGate` implements, and record where the per-kind view `limit` actually lands (#19228)
5+
fix(spec): state the row-cap guard `ElementDataSourceGate` implements (#19228)
66

77
Prose and pins only — zero accept-set movement, zero export movement. The same documents parse
8-
to the same values before and after. ⛔ No `.default()` moves, ⛔ no precedence is picked: which
9-
of the per-kind view `limit`, a view's `pagination.pageSize` and a component's flat `limit`
10-
should win is the open half of #19228 and is not answered here.
8+
to the same values before and after. ⛔ No `.default()` moves.
119

1210
## What the published text said, and what an author can actually reach
1311

@@ -30,31 +28,5 @@ flat `view.limit` (`core/src/data-scope/element-data-source.ts:237-241`), but th
3028
saved-view RECORD as the adapter's `listViews()` returns it — a third face, not an authored view
3129
document. Measured on this tree: `ListViewSchema` REFUSES a flat `limit` with
3230
`unrecognized_keys: ["limit"]`, the verdict a bogus key gets, while the same minimal document
33-
parses with `pagination.pageSize: 50` and with a per-kind `kanban.limit: 50`. No view document
31+
parses with `pagination.pageSize: 50`. No view document
3432
declares a flat `limit` and none carries a tombstone for one.
35-
36-
## Where the per-kind VIEW `limit` lands
37-
38-
⚠️ Two different keys are easy to confuse here, so each statement names its face. The **view
39-
face** is a `ListViewSchema` document's `kanban` / `gallery` / `timeline` block — that is where
40-
this key lives. The **element face** is a page component node's own flat `limit`, declared in
41-
`component.zod.ts`, and that is the key every renderer actually reads. An adapter turns the
42-
first into the second.
43-
44-
The adapters spread a view's per-kind block FLAT onto the node they generate — `...restKanban`
45-
(`plugin-list/src/ListView.tsx:2979`, `plugin-view/src/ObjectView.tsx:1638`; neither destructure
46-
strips `limit`) and `...(viewOptions.gallery || {})` / `...(viewOptions.timeline || {})`
47-
(`ObjectView.tsx:1697` / `:1725`). So a view's `kanban.limit` — including the 100 the applied
48-
default materializes — becomes the node's flat `limit`, which `ObjectKanban.tsx:553` reads. A
49-
view's `timeline.limit` is route-dependent: `plugin-view` flattens it and `ObjectTimeline.tsx:279`
50-
reads it, while `plugin-list` forwards the block nested, where nothing does. A view's
51-
`gallery.limit` is flattened too and read by nobody — `ObjectGallery.tsx` contains no `limit` at
52-
all.
53-
54-
Where it is read, the `$top` it would govern is still not issued on either adapter route today,
55-
because both hosts hand rows down as a React `data` prop and both children short-circuit their
56-
own fetch; ⛔ that is a statement about the query, not about the key being unread.
57-
58-
⚠️ For authors of an `object-timeline` NODE: a `limit` written inside that node's own `timeline`
59-
block is read by no renderer on any route — the rail is capped by the flat `limit` beside it,
60-
which is also the only one a bound `dataSource` lowers into. Write the flat one.

‎content/docs/references/api/protocol.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,7 +1674,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
16741674
| **selection** | `{ type?: Enum<'none' \| 'single' \| 'multiple'> }` | optional | Row selection configuration |
16751675
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; preventNavigation?: boolean; openNewTab?: boolean; size?: Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
16761676
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
1677-
| **kanban** | `{ groupByField: string; summarizeField?: string; titleField?: string; columns: string[]; … }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
1677+
| **kanban** | `{ groupByField: string; summarizeField?: string; titleField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
16781678
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string; … }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
16791679
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … }` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
16801680
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
@@ -1759,7 +1759,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
17591759
| **selection** | `{ type?: Enum<'none' \| 'single' \| 'multiple'> }` | optional | Row selection configuration |
17601760
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; preventNavigation?: boolean; openNewTab?: boolean; size?: Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
17611761
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
1762-
| **kanban** | `{ groupByField: string; summarizeField?: string; titleField?: string; columns: string[]; … }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
1762+
| **kanban** | `{ groupByField: string; summarizeField?: string; titleField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
17631763
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string; … }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
17641764
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … }` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
17651765
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |

‎content/docs/references/data/object.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ const result = ApiMethod.parse(data);
373373
| **selection** | `{ type?: Enum<'none' \| 'single' \| 'multiple'> }` | optional | Row selection configuration |
374374
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; preventNavigation?: boolean; openNewTab?: boolean; size?: Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
375375
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
376-
| **kanban** | `{ groupByField: string; summarizeField?: string; titleField?: string; columns: string[]; … }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
376+
| **kanban** | `{ groupByField: string; summarizeField?: string; titleField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
377377
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string; … }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
378378
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … }` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
379379
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |

‎content/docs/references/ui/component.mdx‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ View filter rule
813813
| Property | Type | Required | Description |
814814
| :--- | :--- | :--- | :--- |
815815
| **objectName** | `string` | optional | Object this timeline binds to. Optional because the component-level `dataSource` binding can supply the object instead — this block registers through `ElementDataSourceGate`, which lowers the binding onto this key before the renderer sees the node |
816-
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline configuration, the author face — the same block `ListViewSchema.timeline` declares: `{ startDateField, endDateField, titleField, groupByField, colorField, scale, limit }`. The flat top-level spellings beside it are the runtime handoff, not a second authoring spelling. ⚠️ `limit` is the one member of this block NO renderer reads on any route: the rail is capped by the FLAT `limit` beside this key, which is also the only one a bound `dataSource` lowers into. A `limit` written inside this block is accepted, defaulted to 100, and never read |
816+
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline configuration, the author face — the same block `ListViewSchema.timeline` declares: `{ startDateField, endDateField, titleField, groupByField, colorField, scale }`. The flat top-level spellings beside it are the runtime handoff, not a second authoring spelling |
817817
| **filter** | `{ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Base query filter — the ViewFilterRule array form `[{ field, operator, value }, ...]`, the one filter orthography every `filter` door in this map shares; lowered to the wire `$filter`. The MongoDB-style record form is refused — see migration `element-data-source-and-object-block-filter-rule-array` |
818818
| **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | Row order for the fetched entries — the SortItem array form `[{ field, order }, ...]`, the one sort orthography every declared `sort` door on this platform shares; lowered to the wire `$orderby`. The legacy string clause (`name desc`) is refused — see migration `object-block-sort-item-array` |
819819
| **limit** | `integer` | optional | Maximum number of records loaded onto the rail (row cap); lowered to the query's top-level `$top` (renderer default 100). A timeline renders one rail with no pagination control, so this is the author's window rather than a page size |
@@ -838,7 +838,6 @@ View filter rule
838838
| **groupByField** | `string` | optional | Field to group timeline rows. NO leading or trailing whitespace: the renderer reads this name off every row verbatim, so a padded spelling drops every row into one ungrouped band. |
839839
| **colorField** | `string` | optional | Field to derive each item color from (it names a field, not a color): the option color declared on that field for the record value, else the value itself when it already is a color literal (hex, rgb() or hsl()), else the timeline default marker color |
840840
| **scale** | `Enum<'hour' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'>` | optional (default: `"week"`) | Default timeline scale |
841-
| **limit** | `integer` | optional (default: `100`) | Row ceiling — the most rows the timeline fetches onto its rail; default 100 when the key is absent. The renderer owes two things: bound its fetch at this number, and, when the ceiling APPLIES (the filtered set is larger than it), show a visible truncation signal saying what is on screen is not the whole set — a bounded view that looks complete is worse than an unbounded one. ⚠️ Not every view kind has a renderer that reads this key yet; which do is recorded on the declaration. |
842841

843842
### Nested Shape: `ObjectTimelineProps.filter[number]`
844843

0 commit comments

Comments
 (0)