Repository navigation
feat(plugin-detail): row caps admit only a positive integer number, and warn on refusal (objectui#10145) - #10309
Conversation
…nd warn on refusal normalizeLimit (record:activity, record:chatter, record:discussion) and normalizeHistoryLimit (record:history) no longer run Number(value) before their integer check. The spec declares limit as z.number().int().positive(), so a string, boolean or array is refused and falls back to the block default. Each renderer states the refusal once through console.warn from an effect, naming the block and the raw value with its type. The four pins that asserted numeric-string coercion are flipped to refusal assertions with lit number controls; the two silence pins are flipped to warning pins.
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
CI blocker, not this PR's:
Generated by Claude Code |
… navigation item type objectstack#19789 widened the spec's navigation item union with `doc`, and `NAV_TYPE_META` is a Record keyed by that spec-derived union, so objectui stopped compiling against @objectstack/spec built from objectstack main (the Spec Main Shape Gate, TS2741 on NavigationDesigner). - `doc` entry: `appDesigner.navTypeDoc`, `bg-blue-100 text-blue-700`, `BookOpen`. - The map is typed `Record<NavigationItemType | 'doc', ...>`: the pinned spec 17.4.0 has no `doc`, so a plain `doc:` key is an excess property there. The `| 'doc'` goes at the pin bump that ships `doc`. - `appDesigner.navTypeDoc` fallback in useDesignerTranslation and all ten locale packs. - Guard test: every discriminant of the installed spec's NavigationItemSchema renders a row with a resolved type badge (spec subset of map, not equality). - `doc` is deliberately NOT in QUICK_ADD_TYPES: an empty doc item fails the spec's book-or-doc requirement; authoring it is objectui#10188. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC (cherry picked from commit 6f970da)
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Closes #10145
Clause-②: no (it narrows, per ruling 5816948508)
Executes ruling 5816948508 (batch #222 item 2, letter A, STOP; maintainer 「同意」): the row-cap renderers read what the contract reads.
What changed
normalizeLimit(recordActivityFeed.ts, shared byrecord:activity,record:chatter,record:discussion) andnormalizeHistoryLimit(record-history.tsx) no longer runNumber(value). Each reads one predicate:typeof value === 'number' && Number.isInteger(value) && value > 0. This is the same predicate the three objectui#9925 read points (reference rail, kanban, timeline) already use.console.warnfrom an effect keyed on the message, the objectui#9925 channel. The warning names the block and spells the raw value with its type, for example[ObjectUI] record:history row cap: declared limit: "5" (string), ....undefinedandnull(unauthored) stay silent.origin/mainbefore the edit, and they already fell back (Number.isInteger/> 0). Their fallback is unchanged, and they now warn as well. The evidence: objectui#10097 ruled A ("always warn", comment 5807911435) for exactly these two silent read points, and it asks that the silence pin not survive. Warning only on the newly refused shapes would have left the family with two answers again. objectui#10097 remains open for the PM to reconcile. This PR covers its two silent read points and adds no warning anywhere else.limitrow incontent/docs/plugins/plugin-detail.mdxnow states the rule..changeset/10145-row-cap-admits-only-numbers.md:minoron@object-ui/plugin-detail, with a migration note (stored stringlimitdrops to the default and warns).Census (ruling item 5), on
origin/main8b1f066git grepforNumber(overpackages/*/src(excluding tests), then for everylimit/pageSize/maxRowsread. I read each hit:normalizeLimitviarecord-activity.tsx(read('limit'))Number(value)normalizeLimitviarecord-chatter.tsx(feed?.limit, bothrecord:chatterandrecord:discussion)normalizeHistoryLimit(record-history.tsx)Number(value)record-reference-rail.tsx,plugin-kanbanObjectKanban.tsx,plugin-timelineObjectTimeline.tsx,plugin-formLineItemsPanel.tsx(isUsableRowLimit)typeof === 'number'record-related-list.tsxtypeof schema.limit === 'number' && > 0toaster.tsxlimit,DatasetWidget.tsxoptions.limittypeof === 'number'app-shellentitlements.tsNumber(details?.limit)data-list.tsxprops.limit,record-picker.tsxlimit ?? 50,ElementDataSourceGate/element-data-source.tsconfig.limitSo there are 2 resolvers of the same shape, serving 4 block types, and both are covered here.
Spec check
@objectstack/spec17.4.0 (installed):RecordActivityProps,RecordHistoryPropsandRecordChatterProps.feedsafeParseanswerinvalid_typefor'5',' 5 ','0x10',trueand[7](and for1.5),too_smallfor0and-3, and OK for5.origin/maine8f163fpackages/spec/src/ui/component.zod.ts: RecordActivitylimit: z.number().int().positive().default(20), RecordHistorylimit: z.number().int().positive().optional().Pins, flipped (not deleted), with lit controls kept
recordActivityFeed.test.ts:normalizeLimit('5')now assertsDEFAULT_ACTIVITY_LIMIT(was 5). Lit control:normalizeLimit(5)is 5.recordActivityFeed.rowLimitRefusal-10096.test.tsx: the helper numeric-string control ('5',' 7 ') now asserts the default, plus'0x10',trueand[7], with lit5and7. The wiretopFor({ limit: '5' })now assertsRENDERER_DEFAULT + 1(was 6), with a new lit controltopFor({ limit: 5 })of 6. The silence pin is flipped to a warning pin (it.eachover 8 refused values, 1 warning each, namingrecord:activityand the typed raw value), with a no-warning control for usable and absent caps.record-history.rowLimitRefusal-10005.test.tsx: the wire'5'now assertsRENDERER_DEFAULT(was 5), plus' 5 ','0x10',trueand[7], with litlimit: 5passing through. The silence pin is flipped to a warning pin (10 refused values), with a no-warning control.record-chatter.rowLimitRefusal-10145.test.tsx:record:chatterandrecord:discussion.feed.limit: 2shows 2 rows silently (lit).'2',' 2 ',trueand[2]show all 6 rows with one warning naming the block.Reverse verification (at the committed head)
I restored the
Number(value)body innormalizeHistoryLimitonly. Proof it landed on disk:grep -c 'const n = Number(value);'answered 1, and the removed line answered 0. I then ran the history pin file: 8 failed | 16 passed (24). The 8 red are exactly the flipped string/boolean/array cases and their warnings. The lit controls and the number-valued refusals stayed green. I restored withgit checkout HEAD --:git diff HEADis 0 bytes, and the blob hash equals the HEAD blob (baa1324e).Gates (head 5cdeedc)
pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail^...' build: exit 0 (12 of 47 projects in scope).pnpm --filter @object-ui/plugin-detail type-check(tsc --noEmit && tsc -p tsconfig.test.json, and the latter includes the tests): exit 0.pnpm exec vitest run packages/plugin-detail/src/renderers/: 55 files, 657 tests passed.eslint --no-inline-configon the 8 changed TS files: 0 errors. Warnings per file match the base; the new test file carries 2no-explicit-anywarnings, the sameitems as anyshape as its siblings.check-changeset-presence: pass.check-changeset-no-major: pass.check:new-line-citations:0 new citation(s).check:control-bytes,check:doc-fences,check:changeset-claimsandcheck:pending-changeset-literals: pass.check:doc-snippetsandcheck:doc-examples. Both exited 2 with PRECONDITION NOT MET, because they need a 34-package build. The docs edit is one table cell of prose with no code fence. Declared to CI.Acceptance notes
record-related-list.tsxadmits a positive NON-integerlimit(typeof === 'number' && > 0, no integer check), and refuses silently. That is a different shape from this card (the number/non-number question is already answered there), so it is left untouched.formatAuthoredLimit/isUsable*predicate is now spelled at 6 sites across 4 packages. Consolidating it is out of scope.Session:
https://claude.ai/code/session_01877XiBYSaRCk2CU7cMSg3S(PM seatdomain:spec#1).Generated by Claude Code