Skip to content

fix(spec): offer the view form's pagination to every view type - #19841

Merged
os-support-ai merged 6 commits into
mainfrom
claude/issue-19814-view-form-pagination-all-kinds
Sep 23, 2026
Merged

os-support-ai merged 6 commits into
mainfrom
claude/issue-19814-view-form-pagination-all-kinds

Conversation

@os-support-ai

@os-support-ai os-support-ai commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Fixes #19814

Clause-②: no

What changes

pagination.pageSize is the row bound every view type carries, and maintainer ruling D on #19228 makes it a view's one row bound. The per-kind kanban.limit, gallery.limit and timeline.limit that the ruling retires are still declared on main until PR #19809 lands. The Studio view form (packages/spec/src/ui/view.form.ts) offered pagination only inside table_options, whose condition is data.type == 'grid' || data.type == null and whose description is Grid-only display options.. An author editing any other view type could not see or set that bound in the form.

  • pagination moves out of table_options into its own section, pagination (label Pagination, collapsible, collapsed), placed right after table_options. The new section has no visibleWhen.
  • table_options keeps resizable, compactToolbar, rowHeight and selection under its unchanged grid condition.
  • Why a new section and not one of the other two options. Widening table_options' condition would show the four grid-only fields to every view type. Moving pagination into columns_filters would put a field that is neither a column nor a filter under the label Columns & filters. The file already gives each concern its own section. The new description claims only what the schema guarantees: Page size and page-size options — every view type accepts them, not only grids.
  • No schema change. view.zod.ts, component.zod.ts and every per-kind limit are untouched; they belong to PR fix(spec): one row bound per view — retire the unpublished per-kind view limit #19809.

Premise, measured on origin/main c1dfa5241b

  • The view type enum is on the list-view shape (ListViewSchema.shape.type). The container ViewSchema has no type key, and its list member resolves to the same nine values: grid, kanban, gallery, calendar, timeline, gantt, map, chart, tree.
  • pagination is a member of the single list-view shape, so every kind has it. Per kind, ListViewSchema.safeParse({ type, columns: ['name'], pagination: { pageSize: 50 } }) parsed 9/9 and kept { pageSize: 50 }. As a control, pagination: { pageSize: 50, zzBogus: 1 } was refused 9/9 with unrecognized_keys at ['pagination'], which shows the block is validated and not stripped.
  • At base, view.form.ts has no per-kind limit entry. Nothing PR fix(spec): one row bound per view — retire the unpublished per-kind view limit #19809 retires is in the form, so this change does not depend on a shape that PR has not landed.

Tests

packages/spec/src/ui/view-form-pagination.test.ts, 58 cases:

  • The kind list is read at runtime from ListViewSchema's type enum. A floor names the four types ruling D covers, so an empty derivation cannot pass.
  • For every kind, and for a view with no type yet, pagination is offered in exactly one visible section. The form offers it exactly once.
  • For every kind, the schema accepts a pagination block and keeps it.
  • The four grid-only fields are visible for grid and for an unset type, and hidden from each of the eight non-grid kinds.
  • Section predicates are read by a small reader limited to the one grammar this form uses: disjunctions of data.type == '...' and data.type == null. Any other term throws and names the predicate, so the pin fails loudly on a predicate it cannot read.

Firing control (a one-time run, not a kept test). With the fix committed, packages/spec/src/ui/view.form.ts was restored to its base blob bea0c5ab92 with git restore --source set to the base commit, and the pin was run. Result: Tests 8 failed | 50 passed (58). The eight failures are exactly the "is visible for type" cases for kanban, gallery, calendar, timeline, gantt, map, chart and tree. The file was then restored from HEAD under a trap; its blob was re-checked equal to HEAD's (bf6c3a7f93) and git status was clean.

Generated artifacts

One repo generator reads view.form.ts: pnpm i18n:extract. It reaches the form through METADATA_FORM_REGISTRY and writes packages/platform-objects/src/apps/translations/*.metadata-forms.generated.ts. A search of the tree for the form's section text finds it only in the source and in en.metadata-forms.generated.ts. The viewForm export keeps its name, and check:api-surface reports the surface unchanged.

  • 17bb7c1603 is the generator output, unedited. It adds the new section's label and description to all four bundles, fills the three translated locales from the source, and adds two metadataForms.view.sections.pagination.* rows per locale to the source-hash tables.
  • 72185706b2 writes the zh-CN, ja-JP and es-ES translations. These leaf values are the only edits these files allow. Each label reuses the locale's existing label for the pagination field. A re-run of the extractor then dropped the source-hash rows by itself, and pnpm check:i18n reports the bundles in sync at head.
  • 2bfeee4a23: object-lifecycle-panel-echo-decisions.test.ts pins the per-locale count of translated .label leaves across the metadata-form catalog. The count moves from 583 to 584, which is the one section label this PR translates in each locale.

Changeset

  • @objectstack/spec ships dist in its files[]. viewForm is exported from ./ui and reaches ./system through METADATA_FORM_REGISTRY. After a build, the new description string is in 6 dist files (ui/index.{js,mjs}, system/index.{js,mjs}, browser/system/index.{js,mjs}). The positive control Grid-only display options is in the same 6 files.
  • @objectstack/platform-objects also ships dist in its files[]. After its rebuild the new string is in 6 dist files (index, plugin and metadata-translations/index, each .js and .mjs); the control is in the same 6.
  • Both packages ship the change, so .changeset/19814-view-form-pagination-all-kinds.md bumps both as patch. AGENTS.md Post-Task step 3: a fix in a released package takes a patch changeset, and skip-changeset is only for a diff that publishes nothing. Clause-②: no, because no accept set moves.

Local verification

  • Gate families at head 0395fd696d (re-run after round 2; first measured at 2bfeee4a23, same result): node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 84 commands. All 84 were run on 2bfeee4a23 and all exited 0. --ran, fed the recorded exit codes, reports: "84 derived famil(ies) accounted for — 84 run, 0 NOT-MEASURED (a DERIVED zero — all 84 recorded an exit code and none of them is 3)". On the first pass, check:i18n, check:type-check-debt and check:dual-build-cjs-loads exited 3 (build prerequisite not met). They were measured after turbo run build --filter='./packages/*' --filter='./packages/*/*'.
  • @objectstack/spec, run at fe2ae4cf09 (the spec package has no byte change since):
    • vitest run --project local: 522 files, 15405 passed, 1 todo.
    • typecheck: exit 0. The test layer holds 53 files / 255 errors in the ledger, and the new test is inside the tsconfig.test.json program.
  • @objectstack/platform-objects at 2bfeee4a23: vitest 54 files, 883 passed. typecheck exit 0.
  • Narrowed lint. This is not the repo-wide pnpm lint, which CI owns. eslint --no-inline-config --format json over the 7 touched .ts files returned 7 file results, 0 errors, 0 warnings, and none of the files was ignored. The repo's one eslint.config.mjs enables no type-aware linting (its note at :326-328), so this diff cannot change the lint result of any file it does not touch.
  • Left to CI: the 7 families whose values come from the workflow (check-issue-citations, shard attestations, test completeness), the 11 declared-wide families, and pnpm lint.

Acceptance notes

  • The renderer side is not this card. At the objectui pin 87af769e9, plugin-list/src/ListView.tsx reads schema.pagination?.pageSize into the $top of every list fetch (:1307-1312, :2356). For non-grid views it shows a rows-per-page selector when pageSizeOptions is set (:4866). The one exception is a gantt view with an api data provider, which does its own fetch (:1950-1955). This PR does not change the plugin-view route's hard-coded $top: 100, which is recorded in review record 5791323483 on PR fix(spec): one row bound per view — retire the unpublished per-kind view limit #19809.
  • The form still has no section for the map and tree blocks that the list-view shape declares. That falls under the top-level "declared but not offered" class, which metadata-form-declared-rows.pin.test.ts counts as its own census, not under this card.

Generated by Claude Code


Generated by Claude Code

`pagination.pageSize` is the one row bound a view carries, and for a
kanban, gallery or timeline view it is the only one. The Studio view
form offered `pagination` only inside the grid-only `table_options`
section, so an author of any other view type could not reach it.

`pagination` moves to its own section with no `visibleWhen`; the
grid-only fields stay in `table_options`. A pin derives the type list
from the list-view `type` enum and asserts both halves.

Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>
…w pagination section

Output of `pnpm i18n:extract`, unedited: the view form's new
`pagination` section adds its label and description to the four
metadata-form bundles, with the three translated locales filled from
the source and their provenance recorded in the source-hash tables.

Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>
Hand-written zh-CN / ja-JP / es-ES leaves for the new section's label
and description (the label reuses each locale's existing `pagination`
field label). A re-run of `pnpm i18n:extract` then dropped the three
source-hash entries by itself, since those leaves are no longer copies
of the source.

Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>
…agination section

Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>
The view form's new `pagination` section adds one `.label` leaf, and it
is authored in zh-CN, ja-JP and es-ES rather than left an extractor
fill, so the per-locale count of translated labels moves by exactly one.

Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 23, 2026
@github-actions

github-actions Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 2 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json afc3b64928914c21db8389e3383233ef18dc4134packageMentionDocs.

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2bfeee4a23c4c149c0c5028f6ec4f356c7ee2ea5

Card #19814, round 1. Base main, merge-base c1dfa5241b; 5 commits, 8 files. Reading moment: 2026-09-23T10:51Z. A record binds ONE head and one reading moment; it is ⛔ not a standing statement about CI.

Tier, measured by the SEAT from the reviewer's transcript (⛔ not its self-report): CONTRACT_REVIEW_TIER on origin/main is claude-fable-5-1. Governing assistant-request stamps 116 / 116 claude-fable-5-1; dark control empty. The fallback/overload sweep, read in context, is tool descriptions and repo source. ⛔ None of it is a notice.AT TIER. This seat is off-tier and ⛔ did not 自审.

① Derived judgments

Blocking — B1: a shipped changeset sentence is false on this head. .changeset/19814-view-form-pagination-all-kinds.md, second paragraph: 「pagination.pageSize is the one row bound a view carries; for a kanban, gallery or timeline view it is the only one.」

Everything else measured holds:

  • The form. The type list is derived at runtime from ListViewSchema.shape.type (9 kinds).
    • pagination is offered exactly once, in a section with no visibleWhen.
    • Parse probe: pagination with pageSize and pageSizeOptions is kept for 9 / 9 kinds. The firing control (a bogus nested key) is refused 9 / 9 at pagination.
    • table_options keeps its four fields under the unchanged grid predicate. No other entry or predicate moves.
    • The section id pagination collides with nothing: section and field keys live in separate subtrees in translation.zod.ts, the resolver and the extractor. The kanban, calendar and other sections already share their field's name.
  • The pin can fail. 58 pass at head. On the base view.form.ts it reads 8 failed | 50 passed, exactly the eight non-grid "is visible for type" cases; restored and hash-checked. Its predicate reader throws on seven unknown grammars that the reviewer injected, rather than passing.
  • The bundles. After restoring the 11 generated files to base, pnpm i18n:extract reproduces commit 17bb7c1603 byte-for-byte, so it is pure generator output. The three translated locales then hand-replace the label and description leaves, which is the repo's convention (AGENTS.md; precedents feat(spec): give the 45 declared-but-unoffered scalar metadata keys a form row each #19673 and fix(platform-objects): decide the lifecycle panel and the email-template sample per leaf — the whole remainder of #19403 #19613). The extractor drops the source-hash rows by itself. pnpm check:i18n exits 0, and each translation matches its locale's existing fields.pagination.label.
  • The count pin, 583 → 584. zh-CN, ja-JP and es-ES each add exactly view.sections.pagination.label and remove nothing. The en leaves equal each locale's (985), and the docblock's positive control still holds.

② Semver level

patch on both packages is right. @objectstack/spec ships the new description in 6 dist files, where the control string 「Grid-only display options」 also appears in the same 6. @objectstack/platform-objects does the same across 6. check:api-surface exits 0, unchanged. Every changeset sentence except B1 checks out.

③ Boundary flags

  • Fixes #19814; line-leading Clause-②: no is right (no schema byte, no export, api-surface unchanged). check-commit-card-trailers exits 0 over all 5 commits. check-governed-merges: 0 of 8 paths governed.
  • The dev's declared deviation, the count pin in platform-objects, is a count this change moves by exactly one label per locale. Accepted.
  • Consumers. In this repo, table_options appears only in the form, the new test and the bundles. objectui at the pinned 87af769e9 has 0 hits on the section names, and ListView.tsx spreads schema.pagination from the view definition, never from a form section.

CI at this head: 35 names — 32 success, 3 skipped (Console Pin Gate, Build Docs, Packed-tarball smoke (opt-in)), 0 in progress / queued / cancelled / failure. All seven required contexts are success.

Non-blocking, recorded and ⛔ not a round:

  • N1. Until fix(spec): one row bound per view — retire the unpublished per-kind view limit #19809 lands, a kanban, gallery or timeline view shows two row bounds in the form: the per-kind composite renders its schema-derived sub-fields, limit included, and pagination.pageSize is now in its own section. This was read from objectui's metadata-form-i18n.ts docblock, not rendered. It resolves itself when fix(spec): one row bound per view — retire the unpublished per-kind view limit #19809 lands, and is what triage's ordering note was about.
  • N2. table_options' description 「Grid-only display options.」 is pre-existing text. objectui's ListView.tsx at the pin applies compactToolbar, rowHeight, resizable and selection beyond the grid kind. Only the PR body's 「is now accurate」 overclaims; it does not ship.
  • N3. NOT MEASURED locally: check:i18n-coverage (exit 3, a build prerequisite). CI's Lint & Repo Gates runs it and is green.

Implemented-by: claude/issue-19814-view-form-pagination-all-kinds
Reviewed-by: session_013RDBh5DqXd2xnLwvHLgLFr

VERDICT: FAIL


Generated by Claude Code

…he per-kind limit retirement

The changeset, the form comment and the pin's docblock said
`pagination.pageSize` is a view's only row bound. The kanban, gallery and
timeline configs still declare a per-kind `limit` until its retirement
lands, so all three now say only that `pagination.pageSize` is the row
bound every view type carries. Wording only; no form entry, test logic,
bundle or translation moves.

Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0395fd696d51b326e5ee68b31bd48a398135a18e

Card #19814, round 2. Base main, merge-base c1dfa5241b. Round-2 range 2bfeee4a23..0395fd696d: 1 commit, 3 files, +8 / −8, all prose. The round-1 record (5793523605) was FAIL on B1; this record binds the new head. Reading moment: 2026-09-23T11:56Z. A record binds ONE head and one reading moment; it is ⛔ not a standing statement about CI.

Tier, measured by the SEAT from the reviewer's transcript (⛔ not its self-report): CONTRACT_REVIEW_TIER on origin/main is claude-fable-5-1. Governing assistant-request stamps 65 / 65 claude-fable-5-1; dark control empty. The fallback/overload sweep, read in context, is tool descriptions and repo source. ⛔ None of it is a notice.AT TIER. This seat is off-tier and ⛔ did not 自审.

① Derived judgments

B1 is resolved.

  • Changeset ¶2 now reads 「pagination.pageSize is the row bound every view type carries.」, with "the one" and "the only one" gone. That is true at head and on origin/main 8cbc3c0084: ListViewSchema declares pagination, and the pin's parse probe keeps { pageSize: 50 } for 9 / 9 kinds.
  • The per-kind limit: rowLimitKey(…) for gallery, timeline and kanban is still declared on both trees. PR fix(spec): one row bound per view — retire the unpublished per-kind view limit #19809 is open and draft. No shipped or commented sentence states it as retired.
  • Ruling D was read in its own words (5789634193: 「视图上只留一个行数上限 pagination.pageSize … 一个视图一个上限」). The body's attribution is faithful, and the comment's 「makes it the direction for a view's row bound」 is faithful and softer.

Nothing else moved.

  • view.form.ts with comments stripped, and the test file with docblocks stripped, are identical between the two heads (diff exit 0).
  • The four generated bundles and the count-pin test have identical blobs.
  • pnpm i18n:extract at head exits 0 and leaves the tree clean. The pin at head is 58 / 58.

② Semver level

patch on @objectstack/spec and on @objectstack/platform-objects is still right. There is no schema byte and no export change. The description ships in 6 dist files per package, with the control string in the same 6. Every changeset sentence was re-checked true.

③ Boundary flags

  • check-commit-card-trailers over c1dfa5241b..0395fd696d exits 0 (6 commits). The body keeps Fixes #19814 and a line-leading Clause-②: no.
  • check-governed-merges: 0 of 8 paths governed.

CI at this head, read after the seat's 11:46Z body edit: 35 names — 30 success, 5 skipped (Check PR Size and Auto Label, whose newest runs were re-fired by the body edit and skipped, after their push runs were success; Build Docs; Console Pin Gate; Packed-tarball smoke (opt-in)). 0 failure, 0 in progress / queued / cancelled. Check Changeset completed success after the edit.

Non-blocking, recorded and ⛔ not a round:

  • N1. The body sentence saying the spec package has 「no byte change since」 fe2ae4cf09 is literally false, because round 2 changed two comment-only packages/spec files. Body-only.
  • N2. The gate-families line names the new head in the seat's prefix, but still says the 84 were run on 2bfeee4a23. Body-only.
  • N3. The test docblock says 「the view types ruling D names」 over a list that includes grid. The ruling names kanban, gallery and timeline. Pre-existing and non-shipping.
  • N4. The body carries two footers, because the platform appended one on the seat's edit.
  • Record 5793523605's N1 stands: the form shows two row bounds for kanban, gallery and timeline until PR fix(spec): one row bound per view — retire the unpublished per-kind view limit #19809 lands.

Implemented-by: claude/issue-19814-view-form-pagination-all-kinds
Reviewed-by: session_013RDBh5DqXd2xnLwvHLgLFr

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

2 participants