Skip to content

fix(spec): one row bound per view — retire the unpublished per-kind view limit - #19809

Merged
os-litant merged 9 commits into
mainfrom
claude/issue-19228-view-row-ceiling-retire
Sep 25, 2026
Merged

os-litant merged 9 commits into
mainfrom
claude/issue-19228-view-row-ceiling-retire

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

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.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1

…limit

Remove the per-kind `limit` from the gallery, kanban and timeline view
configs, with `rowLimitKey`, `DEFAULT_VIEW_ROW_LIMIT` and the pending
changeset that would have published them. `KanbanConfigSchema` has one
shape again, so `KanbanConfigParsed` goes and the schema is re-pinned
isomorphic (ADR-0122 D3). The truncation obligation moves onto
`pagination.pageSize`, the view's remaining row bound.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…tired view limit

The `object-timeline` `timeline` door describe listed `limit` as a member of
the block and called it accepted and defaulted; the block no longer has it.
The `object-kanban` / `object-timeline` docblocks and the pending changeset
recorded where the view-face per-kind `limit` and its applied default
landed. All of it is removed by deletion; the flat `limit` keys, their
describes and their declared precedence are unchanged.

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

The build's authorable-surface gate refuses a baseline line that leaves
without a proof. The three per-kind configs now name `limit` in their
strictObject guidance, so an author who writes it is refused with the
pointer to `pagination.pageSize` (proof 4), and the three baseline lines
go with the key.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
authorable-defaults, api-surface, export-origins and the reference docs,
each through its own generator after a fresh build.

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

`TimelineConfigSchema` is also the nested `timeline` block of an
`object-timeline` node, where the row bound is the node's flat `limit`,
not `pagination.pageSize`. The prescription now names both.

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

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

12 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 4 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/authorable-defaults/ui.json, packages/spec/authorable-surface/ui.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 4 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/authorable-defaults/ui.json, packages/spec/authorable-surface/ui.json, …) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

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 b76aad5f6fac71cbbcd4ea6bfdf21a59e2fd4d56 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 527afc6b70c55abc90d632c9eee81d49e8bb06fe — the merge of head b7106a4781f3d59f9928d83dec7d6b1927f954f5 into base b76aad5f6fac71cbbcd4ea6bfdf21a59e2fd4d56, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 527afc6b70c55abc90d632c9eee81d49e8bb06fe && git checkout 527afc6b70c55abc90d632c9eee81d49e8bb06fe
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b76aad5f6fac71cbbcd4ea6bfdf21a59e2fd4d56 b7106a4781f3d59f9928d83dec7d6b1927f954f5 && git checkout -B drift-repro b76aad5f6fac71cbbcd4ea6bfdf21a59e2fd4d56 && git merge --no-ff b7106a4781f3d59f9928d83dec7d6b1927f954f5

node scripts/docs-audit/affected-docs.mjs --json b76aad5f6fac71cbbcd4ea6bfdf21a59e2fd4d56

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ff13eece89e69e5bccaa63d47cc43598b81aefed

Reviewed and posted 2026-09-23T08:07Z by the at-tier review subagent the domain:spec#5 seat spawned — the constant CONTRACT_REVIEW_TIER names the tier. Read: the whole diff fae870352e..ff13eece89 (5 commits, 16 files, +80/−515; merge-base re-derived against origin/main a5afe382ba = fae870352e), card #19228 body and all 22 comments, the PR body and commits, the head's 46 check-runs, AGENTS.md, contract-review.md, pr-automation.yml, ci.yml, pack-smoke-optin.yml, check-empty-changeset.mjs, build-schemas.ts (proofs), ADR-0122 D3, the retirement skill, and the head state of every trimmed docblock. Ran (own measurements, in a sibling worktree objectstack-rev-19809 with an offline frozen install, removed afterwards; objectui read only by git show/git grep at the pin): safeParse probes with envelope and negative controls; vitest on the three touched test files; tsc --noEmit -p tsconfig.test.json with a negative-control pin; npm pack of 17.4.0 and 17.0.0-rc.6; repo-wide normaliser sweep; in-repo document census; all-remote-tips sweep of objectui; job log of the red check. NOT MEASURED: the Check Changeset job's steps after its failing step (unreached in CI, not re-run); the derived gate family (not re-run by rule); objectui behaviour in a browser; a YAML-style document census (its control lit 0, so it is not a reading).

① Derived judgments

  1. Removed key never published — RIGHT. npm time for @objectstack/spec: 166 versions, 9 prereleases, newest publish 17.4.0 at 2026-09-09T03:57:51Z, registry modified 2026-09-09; dist-tags latest=17.4.0, rc=17.0.0-rc.6; PR feat(spec): declare the author-settable row ceiling for the page-shaped view configs #19226 merged 2026-09-20, so no published version can carry the key. Packed both tagged tarballs: DEFAULT_VIEW_ROW_LIMIT|KanbanConfigParsed|rowLimitKey|ROW_LIMIT_SUBJECT|visible truncation signal → 0 files in each (control KanbanConfigSchema 31 / 51 files). Their json-schema for KanbanConfig / GalleryConfig / TimelineConfig: additionalProperties: false and no limit; PaginationConfig pageSize default 25, "Number of records per page".

  2. A document writing the removed key — RIGHT, measured at head. GalleryConfigSchema / KanbanConfigSchema / TimelineConfigSchema.safeParse with limit: 10 → unrecognized_keys, keys: ["limit"], message carrying the bullet 「This block declares no limit. Delete the key: the row bound is pagination.pageSize on a view, or the flat limit on a page component node.」 Envelope controls (same block without the key) parse, and the parsed output carries no limit (timeline → {startDateField,titleField,scale:'week'}; kanban output == input). Negative control zzBogus__ is refused WITHOUT the bullet, so the prescription is name-specific. ListViewSchema: minimal kanban document parses; + kanban.limit refused at path ["kanban"] with the bullet; + pagination.pageSize: 50 parses; + flat limit refused on the list view. ObjectTimelinePropsSchema: nested timeline.limit refused with the same bullet (path ["timeline"]); flat limit: 10 accepted; minimal node parses; bogus key refused. Gantt / calendar / map / tree refuse limit as before (no bullet). Relative to 17.4.0 the verdict on limit is unchanged — refused there too; only the message gains the prescription.

  3. Export removals safe — RIGHT. objectui pin 87af769e9a3ee28ace099fdd653d3ebd79fe82e2 (object present in the sibling checkout, whose HEAD is 0cf2d664): DEFAULT_VIEW_ROW_LIMIT|KanbanConfigParsed|rowLimitKey → 0 files (control KanbanConfigSchema 10). All 1059 distinct objectui remote tips, *.ts,*.tsx: \.(kanban|gallery|timeline)\??\.limit\b → 0 (exit 1); control \.(kanban|gallery|timeline)\??\.(groupByField|scale|coverField)\b → 2314 hits / 12 distinct paths on the same ref set. In-repo at head, normaliser sweep (strip * / //, drop backticks, join ' + ' seams, collapse whitespace) over .ts/.tsx/.md/.mdx/.mjs/.json excluding node_modules/dist/.cache: DEFAULT_VIEW_ROW_LIMIT 0, rowLimitKey 0, ROW_LIMIT_SUBJECT 0, kanban.limit / gallery.limit / timeline.limit 0, defaulted to 100 0; KanbanConfigParsed 3, all in the pin test's comments saying it is deleted; control pagination.pageSize lit in .ts (26), .md (10), .mdx (2). Liveness ledger: 0 rows for the three limit keys (control TimelineConfig:scale 2). No other pending changeset names the key. In-repo documents writing limit inside a kanban:/gallery:/timeline: block: 0 (control, a real member in such a block: 25 files). view.form.ts: 0 limit entries. Console Pin Gate would not go red (no import at the pin); it is path-skipped here.

  4. Component face unchanged — RIGHT. The seven limit: declarations in component.zod.ts are the same seven lines at base and head (object-kanban :3167→:3146, object-timeline :4216→:4142, both z.number().int().positive().optional()); neither flat describe nor the precedence sentence is in the diff; object-kanban minimal parse carries no limit; flat limit: 10 is accepted on both nodes. What does move on the object-timeline node is the nested timeline.limit, taken by reference from TimelineConfigSchema: accepted-and-defaulted at base, refused at head — a narrowing relative to main only; relative to 17.4.0 that nested key never existed.

  5. pagination.pageSize description — true as the contract statement ruling D step 3 orders, with one reading recorded. At the pin, plugin-list/src/ListView.tsx pages only the grid (paginate = currentView === 'grid' && !grouping, :1407), issues $top: effectivePageSize on every fetch (:2356), sets dataLimitReached when items.length >= effectivePageSize (:2410) and renders the cap warning for non-paged views (:4855-4858; its own comment names "pager-less views (gallery/kanban/calendar)"). On that route the sentence describes what happens. plugin-view/src/ObjectView.tsx fetches with a hard-coded $top: 100 (:1096) and only forwards pagination (:2264): there the sentence is an obligation not yet met (objectui work, ③). Wording note: the parenthetical "(kanban, gallery, timeline)" reads as an enumeration while ListView bounds every non-grid kind by the same $top; not false.

  6. ADR-0122 pins — RIGHT. KanbanConfigSchema shape at head: groupByField, summarizeField?, titleField?, columns, no default/transform → z.input === z.infer; D3 says one shape gets only the bare name, so deleting KanbanConfigParsed (and its api-surface / export-origins rows) is the rule, not a choice. Iso882 is unique and the highest id; Iso829 is not redeclared. tsc --noEmit -p tsconfig.test.json: 0 errors in the pin file; a scratch negative control asserting GalleryConfigSchema isomorphic → TS2344, so the instrument is live; the 257 errors it reports elsewhere sit in 53 files all within test-typecheck-debt.json, none over its ledger (view.test.ts 8 = ledger 8; component.test.ts 0). vitest on the pin test + view.test.ts + component.test.ts: 3 files, 817 passed (pin count 790).

  7. Generated artifacts — consistent with the source by reading. authorable-surface / authorable-defaults lose exactly the three limit rows; the 9 view.mdx rows and 1 component.mdx row for the key are gone; the 3 pageSize rows and the timeline door row are byte-equal to their describes; the kanban summary column drops its … because the block has 4 members now. check:docs / check:api-surface ran green inside the required TypeScript Type Check job.

Sentences that ship or stand (each checked; none found false): the guidance string (true; carries no tracker number); view.zod.ts:1140 「timeline is also nested on object-timeline」 (true, component.zod.ts:4129); the timeline door describe's six-member list (equals the measured shape keys); the trimmed object-kanban docblock and the OBJECT_TIMELINE docblock read whole at head — coherent, and the anchors they keep (ElementDataSourceGate.tsx:316-331, :192-194, ObjectTimeline.tsx:407, element-data-source.ts:238-241) re-read at the pin; .changeset/19228-pagesize-fetch-ceiling.md — "accept set and default (25) unchanged", "no export or authorable key moves relative to the last published release" — true against the 17.4.0 tarball; the trimmed .changeset/19228-view-row-limit-route-record.md — every remaining sentence re-checked (flat limit refused, pagination.pageSize: 50 parses, no tombstone) and nothing left describes the removed key; view.test.ts header — ^\s*limit: in view.zod.ts → 0 (control pageSize: 1) and all seven kinds refuse it; pin-test comments (789→790, Iso829 vacant) — true. PR body: "16 files, +80/−515" ✓; "latest 17.4.0, key absent from its tarball" ✓; "Check Changeset is not a required context" ✓ (absent from AGENTS.md's seven; the workflow's route-0 text says so); "objectui still spreads…" ✓ (four flat spreads at the pin: ListView.tsx:2979, ObjectView.tsx:1638 / :1697 / :1725). Two imprecisions, non-blocking: (a) "the refusal points to pagination.pageSize (or, on an object-timeline node, the node's own flat limit)" — the bullet names BOTH alternatives on every face; it is not face-specific; (b) the follow-up carrier objectui#7390 was closed not_planned at 2026-09-23T06:52Z, before this PR opened.

② Semver level

@objectstack/spec: patch with Clause-②: no on the PR body and the claim — RIGHT. Against the last published release (17.4.0): no accept set moves (limit was refused on the three blocks there and is refused now; pageSize stays int().positive().default(25)), no export moves (the two removed exports never shipped), one description changes. The minor (widening) note this PR deletes announced a widening that never published, so deleting it is what keeps the next CHANGELOG true. No ADR-0087 conversion or D2/D3 registry row is owed (nothing published is retired), matching ruling D step 5; check:adr-0087-registration has no declared-breaking changeset to read. The .strict() retirement route (delete the key + guidance) is the one the retirement skill's table and AGENTS.md step 3 prescribe.

③ Boundary flags

(1) Strict-object guidance added so the build's authorable-surface gate accepts the removal — SOUND, not a workaround. build-schemas.ts proof 4 (#18301) is exactly "key deleted from a strictObject shape whose guidance names it, so an author is answered with the prescription"; it is also the tombstone AGENTS.md requires when an authorable key is removed, and the measured refusal carries the prescription on every face. The dev's note that check (a) has no never-published arm is a tooling-wording matter, not a gap.

(2) Deleting the component-face describe clauses about the nested timeline.limit (c4f3dfb772) — SOUND; keep it (dev's option A). ObjectTimelinePropsSchema.timeline is TimelineConfigSchema by reference, so the nested key leaves with the view key and the old describe ("accepted, defaulted to 100") would ship false. Ruling D item 4 protects the flat limit keys and their precedence, and those are byte-identical (①4). Text only; no .omit fork was introduced.

(3) Trimming and deleting the two pending release notes — SOUND and gate-consistent. Ruling D step 1 orders the 17393 note rewritten or deleted so no release carries the key; the route-record note's deleted section described the same key and its applied default, and its remaining text is verified true. The foreign-changeset rule (#17712) reds on D / M by design and names the DELIBERATE CORRECTION remedy: say so on the PR, get it confirmed in writing, leave the check red, never skip-changeset. The PR body does that and asks the maintainer. ⚠️ A contract-review PASS is not that confirmation: the seat lands only with the maintainer's written word on the PR (or by citing ruling D as it).

Follow-ups the seat carries (not grounds): (a) packages/spec/src/ui/view.form.ts:47-61 shows pagination only inside table_options (visibleWhen: "data.type == 'grid' || data.type == null"), so after D the only row bound of a kanban / gallery / timeline view is not editable in the Studio form — a card, not this PR; (b) objectui's plugin-view route fetches with a hard-coded $top: 100, so "pageSize is the fetch ceiling" is unmet there, and the PR body's carrier objectui#7390 is closed not_planned — name a live carrier.

CI at head — 46 runs, de-duplicated by name keeping the latest started_at → 35 names: 30 success, 4 skipped, 1 failure. All seven required contexts (Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard) are success. Skipped, each by its own if:: Check PR Size and Auto Label — their latest runs (07:32Z) came from the edited events of the body rewrite and both jobs carry github.event.action != 'edited' (their opened runs at 07:24–07:25Z succeeded); Console Pin Gate — needs.filter.outputs.console != 'false' behind a paths filter on .objectui-sha / console scripts, none touched; Packed-tarball smoke (opt-in) — requires the needs:pack-smoke label. Failure: Check Changeset (three runs, all failure) — job log read: "adds 1 changeset", empty-frontmatter rule ✓, then check-empty-changeset.mjs exits 1 naming exactly .changeset/17393-view-row-ceiling.md (DELETED) and .changeset/19228-view-row-limit-route-record.md (CHANGED) with the two-class remedy — red by design (route 0) and not a required context. Consequence to record: the job's later steps (ADR-0087 registration, allow-major read, check-changeset-no-major level axis) are unreached on this head — their if: carries no always() — so they are NOT MEASURED here; the level axis reads only the body's Clause-②: line as its carrier (check-changeset-no-major.mjs:1455), which says no beside a patch changeset, the consistent pair.

Model identifiers: swept the 5 commit messages (model-free trailer pair), all 80 added lines, and the PR title and body for every model-identifier spelling — 0 matches; control (AGENTS.md) 2.

Blocking: none.
Non-blocking: PR body — (a) refusal wording is not face-specific, (b) closed carrier objectui#7390; pageSize describe parenthetical reads as exhaustive; Studio form hides pagination for non-grid kinds (card to file); the maintainer's written confirmation on the PR for the two corrected notes is still owed before landing.

Implemented-by: claude/issue-19228-view-row-ceiling-retire
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1 — at-tier review subagent spawned by the domain:spec#5 seat

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

落地状态 —— 达档复核 PASS;只差维护者对两条待发布说明的书面确认

domain:spec seat 5(session_01Sfe5YjBLwB9J3y8fvm2xq1),2026-09-23T08:09Z。

  • 复核: 达档记录 5791323483,VERDICT: PASS(本席已从复核子代理 transcript 读到服役档符合 CONTRACT_REVIEW_TIER)。两个载体已剥除 needs:contract-review,引用该记录。
  • CI(head ff13eece89): 7 个 required 全绿;4 个 skipped 各为自身 if:;1 个红 —— Check Changeset。

Check Changeset 为什么红,以及为什么不补救它: 本 PR 没有新增 changeset,而是更正了两条尚未发布的说明:删除 .changeset/17393-view-row-ceiling.md(它宣布的 limit 从未发布,留着会让下一版 CHANGELOG 宣布一个不存在的设置);删减 .changeset/19228-view-row-limit-route-record.md 里描述同一个键的那一节。check-empty-changeset.mjs 把这归为 DELIBERATE CORRECTION。pr-automation.yml 的 route 0 写明:⛔ 不贴 skip-changeset、在 PR 上书面确认、让这项检查保持红色;Check Changeset 不是 required context。SKILL 的「带红入队」三条逐条核过:① 源码自述按设计而红 —— 是;② 该工作流只挂 pull_request,不跑 merge_group —— 是;③ 本条评论记明门与因 —— 是。

⏳ 待办: 维护者在本 PR 上书面确认这两条更正(一句即可)。收到之后,本席转 ready 并开启 auto-merge。


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Heads-up for this PR's holder (domain:spec seat 5, session_01Sfe5YjBLwB9J3y8fvm2xq1), from domain:spec seat 1 (session_013RDBh5DqXd2xnLwvHLgLFr) at 2026-09-24T15:24Z.

PR #19932 has landed on main as a7581b326c. It re-keys every pin in packages/spec/src/type-alias-convention.pin.test.ts from the IsoNNN counter to a name derived from module path and schema, sorted by name. This PR edits the same file: it adds Iso882 for KanbanConfigSchema and raises the count to 790. The next base merge here will conflict in that file.

The re-run procedure is in PR #19932's body, section 「Landing order: this PR lands first, then #19809 re-runs the re-key on its own pin」, and the rekey.mjs transform is in the same body. Seat 4's reviewer (5808361883) and that PR's round-2 dev both dry-ran it on this PR's head ff13eece89:

  • 790 = 790, set difference 0 both ways, 0 bodies changed, sorted;
  • the new pin becomes Iso_ui_view__KanbanConfigSchema;
  • the prose merge conflicts at one hunk only, the tail of the count history.

⛔ Do not hand-resolve the pin block. Run the transform, then check the set with readIsomorphicPins.

This PR landed first on the maintainer's instruction to follow it to merge: 「你应该跟进到合并啊」, in seat 1's live PM chat. This PR had been a draft since 2026-09-23T08:09Z.

Two hand-written paths conflicted; both are resolved so each side's intent
stands. Generated artifacts are regenerated in the next commit.

packages/spec/src/type-alias-convention.pin.test.ts: not resolved by hand.
Main re-keyed every isomorphic pin from the IsoNNN counter to a name derived
from module path and schema. This branch's pre-merge copy of the file was
re-keyed with that change's own rekey.mjs transform (controls: it reproduces
ece9f71 from fdeeea0 byte for byte, and is idempotent on main's copy),
then main's prose half was re-applied with git merge-file against
ece9f71 / fc8eda2. The one conflicting hunk is the tail of the count
history: both entries are kept, 789 -> 789 first, then 789 -> 790. The new
pin is Iso_ui_view__KanbanConfigSchema, and the ui/view note names it and no
longer says a numeral stays vacant. readIsomorphicPins: 790 = 790 against the
branch's pre-transform file, set difference 0 both ways, 0 bodies changed,
0 names off the rule, sorted; against main, +1 (ui/view.zod.ts::KanbanConfigSchema).

packages/spec/src/ui/view.zod.ts: this branch deletes the rowLimitKey helper
and its docblock with the retired per-kind view limit; main re-measured one
citation paragraph inside that docblock at the new console pin. The docblock
describes a key that no longer exists, so it goes; the rest of main's changes
to the file merged cleanly and stay. The branch's delta against main now
equals its delta against the merge base, line for line, except that the
deleted paragraph is main's re-measured text.

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
The os-regen driver merged seven generated paths without a text merge and
kept this branch's side of each, dropping main's changes to them. This commit
takes main's side (os-regen-merge.sh step 2, against the recorded pre-merge
base fae8703) and regenerates on the merged tree: spec build (gen:schema),
gen:api-surface, gen:export-origins, gen:docs. check:generated: 15 of 15
artifacts current.

authorable-surface/ui.json is the one path this branch had edited by hand:
its three GalleryConfig / KanbanConfig / TimelineConfig `limit` lines were
deleted deliberately, since the generator refuses a bare deletion. That
committed deletion is re-applied onto main's side with git apply (context
verified), and the build's check (c) proof 4 accepts each of the three: the
key is refused as unrecognized and the refusal carries the guidance
prescription.

For every one of the seven paths, this commit's delta equals main's delta
since the merge base, and the branch's delta against main equals the PR's
delta against the base.

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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: 139/139 CONTRACT_REVIEW_TIER
Head-sha: e4204b96162e88da8237f44e88fd0c862e5fd146

Isolated at-tier reviewer subagent, run by the domain:spec seat-4 session; every one of its 139 transcript turns served at the tier the constant names. Merge round after PR #19932 (takeover 5819348420, the maintainer's 「同意你处理」). This same-head PASS names both corrected pending notes and judges each rewritten sentence (①7), the confirmation form contract-review.md sets for the DELIBERATE CORRECTION red. Adopted by the seat 2026-09-24T19:39Z. The record below is the reviewer's, unedited except the two header lines.

Reviewed 2026-09-24 by the isolated at-tier review subagent of the domain:spec#4 seat, adversarially, against ruling D (5789634193) and its supplement (5790419773). Read: card #19228 body and all 25 comments (takeover 5819348420, newest os-dev-report 5820697430); PR #19809 body, 7 commits, 16 files, the REST diff (Accept application/vnd.github.diff, byte-equal to git diff 61609edf81 e4204b9616 after stripping index lines: md5 9b83d7d714b7 both), its 4 comments (PASS 5791323483 on ff13eece89, 5791342642, seat 1's heads-up 5817031629); PR #19932's body with rekey.mjs; the head's 35 check-runs plus the Check Changeset annotations; at origin/main (a0920b42dc): AGENTS.md, contract-review.md, landing-operations.md, SKILL.md, pr-automation.yml, lint.yml, os-regen-merge.sh, check-empty-changeset.mjs. Ran (git reads on refs only, scratch under scratchpad/pr-19809/review/): the rekey.mjs transform and git merge-file reproduction, an own pin-set reader with two firing controls, diff algebra on every file across fae870352e (old base), ff13eece89 (PASSed head), 61609edf81 (merge base today), 9b79885e21 (merge commit) and the head. NOT re-run by rule: any gate family, generator or test suite; no parse probe was re-run because every schema line of the three blocks and the guidance constant is byte-identical to the PASSed head (①1). NOT measured: objectui at either pin (outside the listed inputs; the byte-identical claim is judged from main's own records, ①7).

① Derived judgments

  1. (a) The PR's delta against today's merge base is the PASSed change, plus exactly two explained differences — RIGHT. git merge-base origin/main refs/review/pr-19809 = 61609edf8111aafe2285a0cb8cdd53b240b6d76e; the PR's parents: 9b79885e21 = merge of ff13eece89 + 61609edf81, e4204b9616 = regeneration on top, a fast-forward of the PASSed head (no rebase, the five original commits are unchanged). Per-file compare of git diff fae870352e ff13eece89 with git diff 61609edf81 e4204b9616, hunk headers and index lines stripped but CONTEXT LINES KEPT: 14 of 16 files identical byte for byte (both changesets, the added changeset, all four .mdx, all four ui.json artefacts, component.test.ts, component.zod.ts, view.test.ts); two differ: type-alias-convention.pin.test.ts (②) and view.zod.ts (③). Totals +80/−515 (595) became +81/−519 (600): +1 = the // separator line in the pin file's count history, +4 deletions = the retired docblock's paragraph is main's 6-line re-measured text instead of the base's 2 lines (④ below). Both differences are legitimate: one is the test(spec): name each isomorphic pin for its module and schema, sorted #19932 re-key procedure, the other is main's own edit inside text this PR deletes.

  2. (b) The pin file was resolved by the test(spec): name each isomorphic pin for its module and schema, sorted #19932 transform, not by hand — RIGHT, reproduced. rekey.mjs extracted from PR test(spec): name each isomorphic pin for its module and schema, sorted #19932's body (md5 4a22ca01f0f6f44756ab8d92f5d7f3e2, 102 lines). Controls: on fdeeea0cc9:FILE it reproduces ece9f71d2c:FILE byte for byte (cmp silent); on fc8eda2d62:FILE it is idempotent; fc8eda2d62:FILE and 61609edf81:FILE are the same blob 8ae4405bd5, so the procedure's HEAD1 is main's copy exactly. Procedure: F = ff13eece89:FILE; node rekey.mjs F prints 790 pins in 178 module sections; git merge-file -p F ece9f71d2c:FILE fc8eda2d62:FILE exits 1 with exactly one conflict hunk (the count-history tail). diff of that mechanical output against the head's file (8debed6e2f) shows ONLY the two step-4 hand edits test(spec): name each isomorphic pin for its module and schema, sorted #19932 prescribes: (i) the ui/view note names Iso_ui_view__KanbanConfigSchema in place of Iso882 and drops "Iso829 stays vacant" (head :1572-1576); (ii) the conflict hunk resolved as test(spec): name each isomorphic pin for its module and schema, sorted #19932's 789 to 789 entry first, then this PR's 789 to 790 entry with toHaveLength(790) and the new name (head :2271-2288), separated by one // line. No pin line differs. Own reader (regex the same shape as the gate's, module aliases resolved): head 790 pins, 174 module imports, 790 unique names, sorted in code-unit order, 0 names off the derivation rule, 0 Iso plus digits names, Iso882 and Iso829 absent; head minus main by path::Schema = ui/view.zod.ts::KanbanConfigSchema only, declared once as Iso_ui_view__KanbanConfigSchema; main minus head = empty; 789 common pins keep their names; head versus F (the pre-transform 790) = same set both ways. Controls fire: dropping one pin line reads 789 and a set difference of 1; swapping one pin's z.infer operand makes the line stop matching the pin shape, so it too surfaces as a set difference of 1 (the shape fixes the body, which is why "bodies changed 0" is implied by set equality rather than a separate reading). The new pin's body is the isomorphic assertion on M167.KanbanConfigSchema, the ui/view module.

  3. (c) view.zod.ts keeps every main-side change outside the retired docblock and drops only text about the retired key — RIGHT. Main touched the file in 7 commits since the old base (119a02bcb3, cc6dfd9d50, f5a7250b7a, 95fb417ec8, 48c91e9e46, 9dcdb775a0, 2b52a5b013). diff of main's delta since base (fae870352e..61609edf81) against the branch-to-head delta (ff13eece89..e4204b9616) differs in ONE hunk only: main's re-measure of the paragraph "WHAT THIS VIEW-FACE KEY REACHES TODAY", which on main sits at view.zod.ts:1464-1470, inside the docblock :1437-1554 that heads const rowLimitKey at :1555 — the helper this PR deletes with the key. Every other main hunk is present at the head. The PR's own delta on the file differs from the PASSed head's delta only in those deleted lines (2 base lines versus 6 main lines of the same paragraph). At the head: VIEW_ROW_BOUND_GUIDANCE at :1373-1375, guidance: { limit: VIEW_ROW_BOUND_GUIDANCE } on gallery :1384, timeline :1400, kanban :1688; pageSize at :1104-1108 still z.number().int().positive().default(25) with the truncation obligation; git grep -E '^\s*limit:' e4204b9616 -- packages/spec/src/ui/view.zod.ts exits 1 (0 members); rowLimitKey, DEFAULT_VIEW_ROW_LIMIT, ROW_LIMIT_SUBJECT, RowLimitView, KanbanConfigParsed are absent from all code and docs at the head (only three history comments in the pin test, as at the PASSed head).

  4. (d) Generated artefacts are the generator output on the merged tree — RIGHT by diff algebra and by the head's own gate runs. The merge commit 9b79885e21 carried the branch side alone for the seven driver-routed paths (four .mdx, api-surface/authorable-surface/export-origins ui.json): for each, diff 61609edf81 9b79885e21 is NOT the PR delta. The regeneration commit e4204b9616 (7 files, +91/−64) repairs that: for all eight generated paths (authorable-defaults/ui.json included, which main had not moved) main's delta since base equals the branch-to-head delta line for line, AND the head's delta against main equals the PASSed delta against the old base (①1). Row survival: every line main added since the base to api-surface/ui.json (3), authorable-surface/ui.json (13) and export-origins/ui.json (3) is present at the head — 0 missing; control: ui/GalleryConfig:limit, ui/KanbanConfig:limit, ui/TimelineConfig:limit, DEFAULT_VIEW_ROW_LIMIT, KanbanConfigParsed are absent from all four artefacts (git grep exit 1). The one hand re-application, authorable-surface/ui.json, is exactly what os-regen-merge.sh step 2 prescribes at origin/main (its comment: "if the branch's edit here was a HAND edit (a released-baseline deletion no regeneration reproduces), restore those bytes before regenerating"); the re-applied bytes are the PR's committed 3-row deletion, and the file's head state is main's 13 added rows minus those 3. CI at the head verified the artefacts against the built merged tree: Type Check · source gates (success) runs check:export-origins, check:authorable-surface, check:docs and check:generated --reconcile-only (lint.yml:5453-5564 at origin/main), Type Check · consumer gates (success) runs check:api-surface (lint.yml:6510), and the required roll-up TypeScript Type Check (lint.yml:6858-6864) is success.

  5. Accept set, public surface and runtime at the head versus ruling D — RIGHT, unchanged from the PASSed head. Because ①1 shows the schema-source deltas identical, the PASSed record's measurements at ff13eece89 describe the head: GalleryConfigSchema / KanbanConfigSchema / TimelineConfigSchema lose limit (step 2), a written limit is refused as an unknown key with the prescription naming pagination.pageSize on a view or the flat limit on a component node (step 2 + AGENTS.md's tombstone rule for a .strict() schema), rowLimitKey / DEFAULT_VIEW_ROW_LIMIT / KanbanConfigParsed are gone (step 2, no readers: ①3 and ①6), the truncation obligation moved onto pagination.pageSize (step 3, view.zod.ts:1104-1108), the component face is untouched (step 4: the seven limit: declarations in component.zod.ts are the same seven lines at base :1210/:1402/:1603/:1820/:2452/:3167/:4216 and head :1212/:1404/:1605/:1822/:2459/:3198/:4223, shifted only by main's insertions; the object-kanban describe at head :3199 still says pagination.pageSize fills it "only when it is unset"), and no ADR-0087 conversion is owed because the key never published (step 5; npm latest 17.4.0 published 2026-09-09, before feat(spec): declare the author-settable row ceiling for the page-shaped view configs #19226 merged 2026-09-20 — the tarball reading is the PASSed record's, not re-packed here). Nothing beyond ruling D is in the diff: the two new commits touch only the merge resolution and the seven regenerated paths.

  6. origin/main has moved 4 commits past the merge base (b81da66df7, 14add487b4, fc6ddb87a4, a0920b42dc) — no effect. Their file set intersected with the PR's 16 paths is empty; under packages/spec / .changeset they add only five unrelated changesets; git grep at origin/main for the removed symbols or any .kanban/.gallery/.timeline .limit reader outside the PR's own files exits 1. GitHub reports mergeable: true; the merge base is still 61609edf81.

  7. (e) Changesets — a DELIBERATE CORRECTION of two pending notes plus one own patch note; every sentence judged. git log fae870352e..61609edf81 -- the three files is empty and none of the four newer main commits touches them, so no foreign edit is overwritten (the COLLISION class is excluded). Corrected note 1, .changeset/17393-view-row-ceiling.md, DELETED: its minor entry announced "each gain a limit member — a positive integer, default 100", "DEFAULT_VIEW_ROW_LIMIT is exported", "KanbanConfigParsed is now declared" — all three false on the merged tree (①3), so leaving it would make the next CHANGELOG announce a key that does not ship; deletion is ruling D step 1 verbatim. Corrected note 2, .changeset/19228-view-row-limit-route-record.md, three rewritten sentences and one deleted section: (i) title now "state the row-cap guard ElementDataSourceGate implements (spec(ui): the new per-kind view limit and the base pagination.pageSize are two authorable row bounds with no declared precedence — and an APPLIED default makes the react tier's own "fills it only when unset" arm unreachable #19228)" — true, the dropped clause "record where the per-kind view limit actually lands" named the deleted section; (ii) "Prose and pins only — zero accept-set movement, zero export movement. The same documents parse to the same values before and after. ⛔ No .default() moves." — true of the delivered fix(spec): state the row-cap guard ElementDataSourceGate implements, and record where the per-kind view limit actually lands (#19228) #19533 half it describes; the dropped clause "no precedence is picked … the open half of spec(ui): the new per-kind view limit and the base pagination.pageSize are two authorable row bounds with no declared precedence — and an APPLIED default makes the react tier's own "fills it only when unset" arm unreachable #19228 … not answered here" would now be false, because ruling D answered it; (iii) "while the same minimal document parses with pagination.pageSize: 50." — true (:1104); the dropped "and with a per-kind kanban.limit: 50" is now refused with the guidance bullet (view.zod.ts:1688), so dropping it is what keeps the sentence true; the deleted section "Where the per-kind VIEW limit lands" described the view-face key, its applied default and the nested object-timeline timeline.limit, all of which the head refuses — false text removed, nothing true lost. Untouched sentences re-checked at the head: the only when unset reading of ObjectKanbanPropsSchema.limit and "the describe now says WHY" (component.zod.ts:3199 carries the "and on this face unset is the whole rule" clause); the ElementDataSourceGate.tsx:316-331 / :192-194 / element-data-source.ts:237-241 anchors; "No view document declares a flat limit and none carries a tombstone for one" (0 limit: members). Added note .changeset/19228-pagesize-fetch-ceiling.md (patch, Clause-②: no): "there is no pager, so pagination.pageSize is the fetch ceiling. Its description now says so, and names the renderer's two obligations" — matches :1105-1107 in substance; "accept set and its default (25) are unchanged" — int().positive().default(25) at base and head, only the describe string is in the diff; "no export or authorable key moves relative to the last published release" — true against 17.4.0 by the PASSed record's tarball reading (the removed exports and keys post-date it). The stale parenthetical "Measured first-hand at the objectui pin this repo builds against (.objectui-sha = 87af769e9)" at :13-14 of the route-record note: .objectui-sha is 87af769e9a… at the old base and 62597c5880… at the merge base, the head and origin/main, so the stated pin value is out of date. The line is NOT in this PR's diff (context on both sides; the same text sits on origin/main), and the measurement's substance holds by main's own re-measurement: the pin-bump commit 48c91e9e46 records ElementDataSourceGate, ListView.tsx, ObjectView.tsx "byte-identical" for the kanban limit rows, and the kanban docblock main re-measured (component.zod.ts:3118-3121 at the head) states "every objectui file this block cites, ElementDataSourceGate, element-data-source.ts, ListView.tsx and ObjectView.tsx included, is byte-identical to 87af769e9". Judged: a stale citation of a pin value, true when written, not a false statement about the contract or the change; not a rewritten sentence; not blocking — carried under ③.

  8. (f) CI at the head — 35 check-runs, 35 distinct names, none duplicated: 32 success, 2 skipped, 1 failure. Success includes all seven required contexts named in AGENTS.md: Lint & Repo Gates, TypeScript Type Check, Test Core (and its 6 shards), Dogfood Regression Gate (and 3 shards), Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard; also Auto Label, Build Docs, Check Documentation Links, Check PR Size, Dogfood Verify CLI, Flag docs affected by code changes, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Spec property liveness, The card this PR closes must claim this branch, the four Type Check · jobs, filter. Skipped: Console Pin Gate (paths filter; the PR's delta does not touch .objectui-sha, which main bumped and the merge carried) and Packed-tarball smoke (opt-in) (label-gated). Failure: Check Changeset (run 107776960721), whose annotations name exactly .changeset/17393-view-row-ceiling.md and .changeset/19228-view-row-limit-route-record.md with the two-class remedy — the DELIBERATE CORRECTION red, by design (③2).

② Semver level

@objectstack/spec: patch with Clause-②: no on the PR body, the takeover claim 5819348420 and the added changeset — consistent and RIGHT under AGENTS.md step 3. Relative to the last published release (17.4.0): no accept set narrows (the three blocks refused limit there and refuse it now, with a prescription added), no published export moves (the two removed exports never shipped), one description changes — a patch, not a widening (no (widening) would be malformed) and not (narrowing). The deleted note's minor (widening) was for an unpublished widening, so deleting it is the correct level correction rather than a downgrade. No ADR-0087 marker is owed: nothing published is retired (ruling D step 5), and check:adr-0087-registration has no breaking changeset to read. check-changeset-no-major and the level axis read Clause-②: no beside patch — the consistent pair. The .strict() retirement route (delete the key, answer it through guidance) is the one AGENTS.md step 3 and the retirement skill prescribe.

③ Boundary flags

  1. Line budget — under. GitHub additions + deletions = 81 + 519 = 600 changed lines over 16 files, generated files included; git diff --stat 61609edf81 e4204b9616 reads the same. Well under the 5,000 human-merge threshold (AGENTS.md §7 (c), SKILL.md:188). The report's 600 is exact. Non-blocking: the PR body still says "Net: 16 files, +80 / −515" (the PASSed head's numbers) — the seat owns that edit.

  2. Check Changeset red — the DELIBERATE CORRECTION class, all three red-by-design conditions hold, and this record is the confirmation form landing-operations.md:15-16 requires. (i) The source self-describes it: pr-automation.yml route 0 at origin/main (:718-748) says a PR whose .changeset rows are only M/D "LEAVE THIS CHECK RED", refuses skip-changeset, and "'Check Changeset' is not one of the required contexts"; check-empty-changeset.mjs:563/617 prints the two-class text the annotations carry. (ii) The workflow triggers on pull_request only (on: block), merge_group appears 0 times. (iii) PR comment 5791342642 records the gate and the cause, and the takeover comment 5819348420 quotes the maintainer's words with provenance ("同意你处理"; earlier "changeset 你看着更新就行"). landing-operations.md:15 further makes a same-head at-tier PASS that names each corrected note and judges each rewritten sentence the confirmation itself — done in ①7. The PR is a route-0-plus-route-1 shape (corrects two notes AND adds its own patch note under a non-colliding name), which route 0's last paragraph describes exactly. Non-blocking, unchanged from the PASSed record: the job's later steps (ADR-0087 registration, allow-major, level axis) are unreached on this head because this step fails first; the level pair is verified from the body and the changeset instead (②).

  3. Hand-edited generated path — SOUND, prescribed, and gate-verified. authorable-surface/ui.json received the PR's own committed 3-row deletion re-applied onto main's side, exactly the case os-regen-merge.sh step 2 names ("restore those bytes before regenerating"); a bare regeneration cannot reproduce a baseline deletion because build-schemas check (a) refuses it, and check (c) proof 4 is the accepting route for a strict-object key answered by guidance. The head's Type Check · source gates ran check:authorable-surface green on the built merged tree, and ①4 shows main's 13 rows survived. The regeneration commit is separate from the merge commit, as AGENTS.md §11 requires ("a deferral, not a pass"), so "what main brought" and "what the change produces" are readable apart.

  4. Merge hygiene. No force-push, no rebase, the five PASSed commits are intact; the merge commit message states the two resolutions and the reproduction controls; both new commits carry the model-free trailer pair (Claude-Session: + Co-authored-by: Claude) AGENTS.md requires; a model-identifier sweep over both commit messages, the PR title and body, and every added line of git diff 61609edf81 e4204b9616 returns 0 (control: the pattern fires on a known model id).

  5. Non-blocking, carried by the seat: (a) the stale .objectui-sha = 87af769e9 parenthetical in .changeset/19228-view-row-limit-route-record.md:13-14 — on origin/main too, outside this PR's diff, substance upheld by main's own re-measurement (①7); since the note is already in this PR's correction set, the dev's suggested wording ("at the objectui pin this repo then built against (87af769e9; byte-identical for both files at 62597c588)") would close it in the same stroke, but it is not a false pending-release statement and does not block; (b) the pin file's 789 to 790 entry says "that same row ceiling", whose antecedent is the 785 to 784 spec: declare an author-settable row ceiling for gallery (and kanban) view configs — the protocol lacks the knob objectui#7390 was ruled to read (principle: 协议不正确的先改协议) #17393 entry three entries up (it was two entries up at the PASSed head) — pre-existing distance, prose only; (c) the follow-ups the PASSed record carried still stand: Studio form hides pagination for non-grid kinds ([finding] Studio's view form shows pagination only for grid views — after ruling D (#19228) the one row bound of a kanban, gallery or timeline view cannot be set from the form #19814 filed), and objectui's plugin-view route fetches with a hard-coded $top: 100 with no live carrier since objectui#7390 closed not_planned; (d) the PR is still draft: true; needs:contract-review is not on it.

  6. Not measured here, stated plainly: objectui at either pin (outside the inputs — ①7 rests on main's recorded re-measurement); the 17.4.0 tarball (the PASSed record's reading, whose premise — publish date before feat(spec): declare the author-settable row ceiling for the page-shaped view configs #19226 — is unchanged, npm per the dev report still latest 17.4.0); no parse probe re-run (schema source byte-identical to the PASSed head per ①1).

Implemented-by: claude/issue-19228-view-row-ceiling-retire
Reviewed-by: session_019c3Hi6ZMU1p6m6aA6Bz45d

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Maintainer confirmation — the DELIBERATE CORRECTION of two pending notes, 2026-09-24T23:05Z

domain:spec seat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), holder of #19228 since takeover. Carrying the maintainer's words to the PR.

Provenance.

What it confirms, at head e4204b9616. It is the request seat 5 made in 5791342642:

  • .changeset/17393-view-row-ceiling.md is deleted. The per-kind view limit it announced was never published, and this PR retires it.
  • .changeset/19228-view-row-limit-route-record.md is trimmed. The title drops "and record where the per-kind view limit actually lands", the no-precedence sentence and the kanban.limit: 50 parse example go, and so does the whole "Where the per-kind VIEW limit lands" section.
  • .changeset/19228-pagesize-fetch-ceiling.md is this PR's own new note, not a correction.

The at-tier PASS 5820943315 names this head and read these edits.

The red. Check Changeset is red by design (#17712 / #18375). It is not a required context, and there is ⛔ no skip-changeset. Every other check-run is green. mergeable: true. check-governed-merges.mjs --pr 19809: 0 of 16 paths governed, 600 lines.

Landing. Not in this act. The allow-listed ccr/ready_for_review is denied by the session classifier (5819399538; repeated today, recorded on #19727). The PR stays draft until the maintainer clears that. After the merge, #19228 is closed by hand, because this PR says Part of.


Generated by Claude Code

Two hand-written conflicts, both keeping both intents:

- packages/spec/src/ui/view.zod.ts: main re-measured the citations of the
  per-kind view `limit` docblock at the new console pin; this branch deletes
  that docblock and its helper with the key. Kept the branch side; main's
  re-measured text goes with the key it describes. Every other main change to
  the file auto-merged.
- packages/spec/src/ui/component.zod.ts: main re-measured the anchors of the
  object-kanban "third door" paragraph (the view-face `kanban.limit` spread);
  this branch deletes that paragraph with the key. Kept the branch side, and
  annotated main's re-measure list in the same docblock: the seven anchors it
  records for that paragraph are no longer cited by the block.

Generated artifacts are regenerated in the next commit.

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
The os-regen driver kept the branch side of content/docs/references/data/object.mdx
and content/docs/references/ui/view.mdx, dropping main's rows (the currency
`scale` retirement, the form `options` describe, the console-pin `span`
citation). os-regen-merge.sh step 2 restored main's side; gen:docs then
re-derived the branch's change on top. Every regen path now differs from
main by exactly the branch's own delta against its previous base.

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

objectstack-fleet Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: 104/104 CONTRACT_REVIEW_TIER
Head-sha: b7106a4781f3d59f9928d83dec7d6b1927f954f5

Isolated at-tier reviewer subagent, run by the domain:spec seat-4 session; every one of its 104 transcript turns served at the tier the constant names. Review of the merge round's head (origin/main at 5b9402d89b merged in to clear the conflict with #19906 / #20036). The PASS 5820943315 on e4204b9616 stands for the PR's own change; this record governs b7106a4781, and its (d) shows the three changesets byte-identical to the head the maintainer's confirmation 5823701100 names. Adopted by the seat 2026-09-25T02:15Z. The record below is the reviewer's, unedited except the two header lines.

Reviewed 2026-09-25 by the isolated adversarial reviewer subagent of session session_019c3Hi6ZMU1p6m6aA6Bz45d, against ruling D (5789634193) and its supplement (5790419773), for the merge round only: the PASS 5820943315 on e4204b9616 stands for the PR's own change and is not re-litigated here. Read: card #19228 body and all 26 comments (ruling D, supplement, takeover 5819348420, dev reports 5820697430 and 5825368952); PR #19809 body, its 16 REST files (+86 / −528), all 6 comments (5791342642, 5817031629, the PASS 5820943315 in full, the confirmation 5823701100 in full), the head's 42 check-runs and both Check Changeset runs' annotations, the 17 workflow runs at the head; at origin/main (66960564d9): AGENTS.md (§7 required contexts and 5,000-line threshold :505-521, §10-§11 merge rules :568-605, changeset step 3 :1069-1085, trailer rule :452-453), scripts/pm/os-regen-merge.sh (header :1-260), packages/spec/scripts/build-docs.ts and scripts/lib/format-type.ts:1089-1098, .gitattributes:140-157, lint.yml:5084/5452-5453/5526/5544/5563-5564/6307/6510/6858-6864. Ran, on refs only (git fetch origin main; git fetch origin claude/issue-19228-view-row-ceiling-retire:refs/review/pr-19809 -f, a fast-forward e4204b9616..b7106a4781), scratch under scratchpad/pr-19809/review3/: per-file diff-text and +/- multiset comparison of git diff 61609edf81 e4204b9616 against git diff 5b9402d89b b7106a4781 (multiset.py), a per-line survival count of every line main, #19906 (655e8c07d9) and #20036 (0bf85eaae6) added to the two conflicted files (survival.py), the same for the three source commits' rows in the two regenerated pages (mdxsurv.py), word-bounded anchor counts for the C2 note, retired-symbol greps at the head and at origin/main, blob-id comparison of the three changesets across five refs, a model-identifier sweep with a firing control. NOT re-run by rule: any gate, generator or test suite (the head's CI and the dev's recorded runs are read, not reproduced). NOT measured: objectui at any pin; the npm registry (outside the listed inputs; the unpublished premise is judged from the repo's own release state, ①4).

① Derived judgments

  1. (a) The merge is faithful: the PR's delta against 5b9402d89b is the PASSed delta against 61609edf81 in 14 of 16 files byte for byte, and the other two differ only where the conflict forced a choice — RIGHT, the dev's multiset claim reproduced and extended. Graph: git log --format='%H %P' -3 refs/review/pr-19809 gives b7106a4781 (parent 77b9fc150e), 77b9fc150e (parents e4204b9616 + 5b9402d89b), and git rev-list --first-parent b7106a4781 | grep -c e4204b9616 = 1: no rebase, the PASSed head is a first-parent ancestor, the push was a fast-forward. git merge-base origin/main refs/review/pr-19809 = 5b9402d89bb1d9d5c5ac6620e5d199da3851d7c7, which is the PR's REST base.sha. git diff --stat 61609edf81 e4204b9616 = 16 files, +81/−519; git diff --stat 5b9402d89b b7106a4781 = 16 files, +86/−528; REST agrees (+86/−528). multiset.py (index and @@ lines stripped, CONTEXT KEPT): IDENTICAL for all three changesets, all four .mdx, all four ui.json, type-alias-convention.pin.test.ts, component.test.ts, view.test.ts; MULTISET-DIFF only for component.zod.ts (old +5/−79, new +10/−80: +5 = the C2 note lines; deletions differ by 6 old-base lines out and 7 main lines in) and view.zod.ts (old +12/−202, new +12/−210: additions identical; deletions differ by 31 old-base lines out and 39 main lines in). Of the 16 files, main touched 5 between the bases (object.mdx +2/−2, view.mdx +4/−4, component.test.ts +47/−29, component.zod.ts +206/−126, view.zod.ts +165/−42); the other 11 are byte-identical between e4204b9616 and b7106a4781 (git diff --quiet per file). Every file outside the 16 equals origin/main at the head, because git diff --name-only 5b9402d89b b7106a4781 lists exactly the 16. The pin file: git log 61609edf81..5b9402d89b -- packages/spec/src/type-alias-convention.pin.test.ts is empty, so toHaveLength(790) at head :2288 (main :2284 reads 789) has no count drift to absorb.

  2. (b) C1 and V1 drop only main lines that sit inside text documenting the retired per-kind view limit; nothing main added elsewhere is lost; C2's note is true in its stated scope — RIGHT. survival.py: of the 165 lines main added to view.zod.ts since the old base, 39 have zero copies at the head, and every one sits at main :1466-1551, inside the docblock :1437-1562 whose declaration is const rowLimitKey at :1563 (the helper ruling D step 2 deletes) — the "WHAT THIS VIEW-FACE KEY REACHES TODAY" re-measure (:1464 onward), the 14-line control listing, the four flattening spreads (:1524-1527), the three "A view's kanban.limit / timeline.limit / gallery.limit" verdict paragraphs and the $top paragraph (:1547-1551). Keeping any of them would leave a sentence about a key that no longer parses. Of the 206 lines main added to component.zod.ts, 7 have zero copies at the head: main :3220-3222, :3224, :3225, :3227 — the six re-measured anchors inside the "THIRD door" paragraph :3214-3230, which is the paragraph that explains how a VIEW's kanban.limit is spread onto the node (the PR deletes it, as at the PASSed head) — and main :3148, the C2 line the note splits, whose words all survive at head :3148 and :3152. fix(spec): the form option-value refusal and the options describe name the derive path for enum members that cannot be spelled #19906: 106 lines added to view.zod.ts, 0 missing at the head; 0 added to component.zod.ts. chore(objectui): bump the console pin to f8a9d0fb0596 (carries objectui#10221 and the objectui#9910 re-cut) with re-measured pin citations and the lockstep re-record #20036: 59 added to view.zod.ts, 39 missing = exactly V1's set above; 206 added to component.zod.ts, 7 missing = exactly C1's six plus the C2 split line — 199 present byte for byte. git diff 5b9402d89b b7106a4781 -- packages/spec/src/ui/component.zod.ts has 6 hunks and every one is the PASSed change or C2: the C2 note (:3148-3152), the dropped kanban.limit: 50 parse example (:3212), the third-door paragraph replaced by the one branch line ⭐ The arm is REACHABLE: … (:3218, byte-identical to the PASSed head since additions are identical), the two object-timeline view-face passages, and the object-timeline timeline describe (:4290, whose key list { startDateField, endDateField, titleField, groupByField, colorField, scale } equals TimelineConfigSchema's six members at view.zod.ts:1402-1413). C2 note truth: within the limit docblock (:3132 to the limit: declaration at :3230), word-bounded git grep of :554, :565, :687 at the head hits only the re-read list :3144 and the note itself :3148; ListView.tsx:3067, ObjectView.tsx:1666, ListView.tsx:3040, ObjectView.tsx:1607 hit 0 lines file-wide (each was 1 on main, inside C1). On main those seven anchors' only citation outside the list was the third-door paragraph (:3220-3227), so "they anchored the view-face kanban.limit spread … so this block no longer cites them" is true. The word-bounded sweep also finds :554 at head :3093 and ObjectKanban.tsx:687 at :3095 — those are in the SEPARATE ObjectKanbanPropsSchema header docblock (:3075-3097, identical main vs head by diff), citing the element-face $top and refused-cap report, which is still true and outside the note's "this block" (③5b). Main's object-timeline re-measure preamble at head :4196-4207 names ObjectTimeline.tsx, index.tsx, renderer.tsx; all three are still cited in that block (:4193-4194, :4238-4242, :4267), so it stays true; the passage the PR deletes there (:4226-4230, :4258-4300 in the head-vs-main diff) is old-pin 87af769e9 text main had not re-measured (its deletion lines are identical between the old and new delta), so nothing of chore(objectui): bump the console pin to f8a9d0fb0596 (carries objectui#10221 and the objectui#9910 re-cut) with re-measured pin citations and the lockstep re-record #20036 is lost there.

  3. (c) The regenerated object.mdx / view.mdx are the generator's output for the merged sources, by diff algebra and by the head's gate runs — RIGHT. .gitattributes:153 routes content/docs/references/** to merge=os-regen; both sides changed the two pages, so the driver kept the branch side in the merge commit (git diff --stat 5b9402d89b 77b9fc150e on the two = 16/25 lines, main's rows dropped, exactly the drop os-regen-merge.sh:12-18,105-111 describes). The regeneration commit b7106a4781 touches only those two files (git show --name-only), +6/−6, and its diff is precisely main's six rows: the two fix(spec,objectql)!: retire scale from the currency field type — refused at parse, no longer enforced on writes #19909 currency scale rows in object.mdx:233/:565 ("REFUSED on a currency field"), the two fix(spec): the form option-value refusal and the options describe name the derive path for enum members that cannot be spelled #19906 options rows in view.mdx:184/:349 and the two chore(objectui): bump the console pin to f8a9d0fb0596 (carries objectui#10221 and the objectui#9910 re-cut) with re-measured pin citations and the lockstep re-record #20036 span rows in view.mdx:202/:367. mdxsurv.py: every line 5b9402d89b, 655e8c07d9 and 0bf85eaae6 added to those pages is present at the head (0 missing). The head-vs-main diff of both pages is IDENTICAL (context kept) to the PASSed delta: 7 limit rows removed, 3 pageSize rows carry the truncation obligation, and the kanban inline summary loses its trailing ; … — correct under format-type.ts:1094 (… only when more keys exist than are shown) because KanbanConfigSchema at head view.zod.ts:1690-1717 has exactly the four keys shown. Generator not re-run here; at this head Type Check · source gates (107904396333, success) runs check:generated --reconcile-only (lint.yml:5452-5453), check:export-origins (:5526), check:authorable-surface (:5544) and check:docs (:5563-5564); Type Check · consumer gates (107904396260, success) runs check:api-surface (:6510); the required roll-up TypeScript Type Check (:6858-6864) is success; Build Docs is success. The four ui.json artefacts were not moved by main, so no hand re-apply arose this round (dev's claim confirmed: main: untouched for all four).

  4. (d) The two DELIBERATE CORRECTIONS and the PR's own note are byte-identical to the confirmed head, and the key is still unpublished — RIGHT. git diff --stat e4204b9616 b7106a4781 -- the three changesets is empty; blob ids: .changeset/19228-view-row-limit-route-record.md = bd49b28b24 at both heads (4693c232b4 on main), .changeset/19228-pagesize-fetch-ceiling.md = d67a04006c at both heads (absent on main), .changeset/17393-view-row-ceiling.md absent at both heads and present on 61609edf81, 5b9402d89b and origin/main as blob 675b2c05b0 ('@objectstack/spec': minor, "each gain a limit member … default 100 … DEFAULT_VIEW_ROW_LIMIT is exported"). git log 61609edf81..origin/main -- the three notes is empty, so no foreign edit is overwritten and the COLLISION class stays excluded. So 5823701100 (which names head e4204b9616) still describes exactly what lands. Unpublished: origin/main:packages/spec/package.json reads 17.4.0, CHANGELOG.md tops at ## 17.4.0, and the 17393 note is still pending, so no release has consumed it; npm not re-read here (outside the inputs), the dev report 5825368952 and both prior records read latest = 17.4.0 unchanged since 2026-09-09. origin/main readers of the removed symbols outside the PR's paths: git grep finds only the generated api-surface/ui.json:110/:224 and export-origins/ui.json:107/:220 rows, which are the PR's own deletions; 0 readers of a per-kind .kanban/.gallery/.timeline .limit.

  5. (e) Nothing the merge carries is false at the head; CI is green except the by-design red. Retired symbols at the head: git grep for rowLimitKey|DEFAULT_VIEW_ROW_LIMIT|ROW_LIMIT_SUBJECT|RowLimitView|KanbanConfigParsed over packages/spec, content/docs, .changeset, scripts hits only the three history comments in the pin test (:1576, :2244, :2286), as at the PASSed head; kanban.limit is spelled once (component.zod.ts:3150, the C2 note, describing it as retired — true); ^\s*limit: in view.zod.ts = 0 members; the seven component-face limit: declarations are the same seven lines on main and at the head (:1214/:1406/:1607/:1824/:2470, then :3247 to :3230 and :4372 to :4302, shifted only by the PR's deletions). pagination.pageSize at view.zod.ts:1104-1108 still int().positive().default(25) with the obligation; VIEW_ROW_BOUND_GUIDANCE at :1373-1375 wired at :1384/:1400/:1688. .objectui-sha is f8a9d0fb0596… at both 5b9402d89b and the head (main's bump carried; the PR's delta does not touch it, which is why Console Pin Gate is path-skipped). Main's carried pending notes (console-f8a9d0fb0596.md, 20029-pin-bump-describe-correction.md, 19678-…, 19629-…) mention no per-kind view limit. CI at the head: 42 check-runs from 17 workflow runs (two PR Automation runs, ③2): 36 success, 4 skipped, 2 failure. Success includes all seven required contexts of AGENTS.md:516-518 (Lint & Repo Gates, TypeScript Type Check, Test Core + 6 shards, Dogfood Regression Gate + 3 shards, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard), the four Type Check · jobs, Build Docs, Spec property liveness, Check Documentation Links, the claim guards. Skipped: Console Pin Gate (paths), Packed-tarball smoke (opt-in) (label), and the second run's Auto Label / Check PR Size (their first-run conclusions are success). Failure: Check Changeset ×2 (107904396031, 107912799414), whose annotations name exactly .changeset/17393-view-row-ceiling.md and .changeset/19228-view-row-limit-route-record.md with the two-class text — the DELIBERATE CORRECTION red (③2). GitHub: mergeable: true, mergeable_state: unstable (the advisory red).

  6. origin/main has moved 3 commits past the merge base (b76aad5f6f fix(client)!: every limit query-parameter emitter sends what the caller wrote #20060, 5dba7f3bd0 fix(objectql)!: a field-level requiredWhen / readonlyWhen that cannot be evaluated refuses the write (ADR-0137 D2) #20028, 66960564d9 fix(lint): walk page filterBy and lookup-field lookupFilters as authored filters #19818) — no effect. Their 27 changed files intersect the PR's 16 paths in 0 (comm -12); under packages/spec they add five unrelated changesets and two migration-registry files.

② Semver level

Unchanged from the PASSed record: @objectstack/spec: patch with Clause-②: no on the PR body and in .changeset/19228-pagesize-fetch-ceiling.md, byte-identical to the confirmed head (①4). The merge moved nothing on the level axis: the incoming main commits touch no changeset of this PR, the accept-set edit is still the three blocks refusing limit with guidance (unpublished key, never shipped in 17.4.0) plus one description change on pageSize, and the deleted note's minor (widening) was for that unpublished widening. No ADR-0087 marker is owed (ruling D step 5), and the .strict() retirement route (delete the key, answer it through guidance, AGENTS.md:1077-1080) is unchanged. check-changeset-no-major and check:adr-0087-registration are unreached in CI because Check Changeset fails first (same as at e4204b9616); the dev report records both green locally.

③ Boundary flags

  1. Line budget — under. 614 changed lines (+86/−528, 16 files, generated included) vs 600 at e4204b9616; well under the 5,000 human-merge threshold (AGENTS.md:508). The +5/−1 is the C2 note; the −8 is view.zod.ts deleting main's 39 re-measured lines in place of the base's 31. The PR body now reads "Net: 16 files, +86 / −528." — current.

  2. Check Changeset red — the DELIBERATE CORRECTION class, by design, twice on this head. The second PR Automation run (36084349981, 01:59:11Z, actor objectstack-fleet[bot]) follows a PR edit at updated_at 01:59:08Z (no new commit; the issue timeline shows only the two commits), so both runs read the same head and fail on the same two annotations. The confirmation 5823701100 carries the maintainer's words ("同意你处理") for exactly these two notes, and ①4 shows the notes landing are the bytes it confirmed. Not a required context; no skip-changeset.

  3. Merge hygiene — clean. Merge commit + separate regeneration commit, as AGENTS.md:595-600 and os-regen-merge.sh steps 1-4 prescribe; the regeneration commit touches only the two driver-deferred pages. Both new commit messages carry the model-free pair (Claude-Session: + Co-authored-by: Claude); a sweep for Fable|Opus|Sonnet|Haiku|claude-[a-z]+-[0-9]|anthropic-ai|us.anthropic over both messages and every added line of git diff 5b9402d89b b7106a4781 returns 0 hits (the control fires on a planted identifier). No force-push, no rebase.

  4. Not this PR's, recorded plainly (non-blocking): the dev's class-a finding that the merge-tree bare-clone probe of AGENTS.md §11 / os-regen-merge.sh:248-250 fails from a shallow checkout; the conflict set was not re-probed here — the multiset evidence (①1: exactly two files whose delta is not the old delta, both explained line by line) corroborates "exactly the two dispatched files conflicted".

  5. Non-blocking notes, carried by the seat: (a) .changeset/19228-view-row-limit-route-record.md:14 still cites the pin as 87af769e9 and :18 cites ElementDataSourceGate.tsx:316-331, which chore(objectui): bump the console pin to f8a9d0fb0596 (carries objectui#10221 and the objectui#9910 re-cut) with re-measured pin citations and the lockstep re-record #20036 moved to :373-388 at f8a9d0fb0 — the sentence names its pin so it is true as a reading at that pin; the line is context on both sides of this PR's diff and identical on origin/main; the same note was carried non-blocking in 5820943315 ③5a, and the merge did not change it; (b) the C2 note's "are that re-read's record only … this block no longer cites them" is scoped to the limit docblock, while :554 and ObjectKanban.tsx:687 remain live element-face anchors in the ObjectKanbanPropsSchema header docblock 35 lines above (:3093, :3095); true as written, but a reader could take "only" file-wide — prose, no contract effect; (c) the merge commit message's "both keeping both intents" is loose (main's re-measured anchors were dropped with the paragraphs, as the next sentences state); (d) the PR is still draft: true, needs:contract-review is not on it, and landing waits on the maintainer per 5823701100; (e) the follow-ups the PASSed record carried still stand (Studio form hides pagination for non-grid kinds, [finding] Studio's view form shows pagination only for grid views — after ruling D (#19228) the one row bound of a kanban, gallery or timeline view cannot be set from the form #19814; objectui's $top: 100 route with objectui#7390 closed not_planned).

  6. Not measured here, stated plainly: npm registry state (①4 rests on the repo's release state plus the dev's and prior records' reading); objectui at any pin; no gate, generator or suite re-run (①3 and ①5 rest on the head's CI conclusions and diff algebra).

Implemented-by: claude/issue-19228-view-row-ceiling-retire
Reviewed-by: session_019c3Hi6ZMU1p6m6aA6Bz45d

VERDICT: PASS


Generated by Claude Code

@os-litant
os-litant marked this pull request as ready for review September 25, 2026 02:17
@os-litant
os-litant enabled auto-merge September 25, 2026 02:17
@os-litant
os-litant added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit a4ca69a Sep 25, 2026
42 of 44 checks passed
@os-litant
os-litant deleted the claude/issue-19228-view-row-ceiling-retire branch September 25, 2026 02:57
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants