Repository navigation
fix(core,react): a refused dataSource binding limit is not authored and yields to the view's cap (objectui#10016) - #10389
Merged
objectstack-fleet[bot] merged 4 commits intoSep 24, 2026
Conversation
…nd yields to the view's cap (objectui#10016) The binding's own `limit`, the other operand of the row-cap chain, gets the positivity check objectui#9928 gave the saved view's. A refused binding cap (0, negative, non-integer, non-number) is not authored: the chain falls through to the view's usable cap, then to the consumer's own default. - composeElementDataSource: `bindingLimit(config) ?? savedViewLimit(view)`. - elementDataSourceRefusedLimitMessage: optional `binding` and `operand` parameters; the binding operand has its own sentence naming the binding, and the view operand is silent when the binding's usable cap is used. - ViewDataProvider reports both operands; its view warning no longer fires under a usable binding cap (the defect carried on the card). - ElementDataSourceGate: the view cap that replaces a refused binding cap is a baseline a usable component cap wins over, and the binding refusal is reported once per declaration from an effect. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
…d is the core builder's Ablating the builder's binding condition reddens it, so by this suite's naming convention it is not a control. Moved into its own block. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
Contributor
✅ 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
|
…ed row-cap precedence (objectui#10016) The gate's observable precedence changes: when the binding cap is refused, a usable component cap now wins over the view cap, and the gate reports a new refusal. Behaviour-changing work is minor in this repo; major is banned. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
Contributor
✅ 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
|
…claimed but no test held (objectui#10016) Component cap 50 + binding 0 + refused view cap; component cap 0 + binding 0 + view with no cap; component cap 0 + binding 0 + refused view cap. Each asserts the written cap and the exact warnings the PR's truth table states. Tests only. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
Contributor
✅ 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
|
objectstack-fleet
Bot
deleted the
claude/issue-10016-binding-limit-not-authored
branch
September 24, 2026 22:26
This was referenced Sep 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #10016
Clause-②: yes
Ruling
5749674763(batch #198 item 4, letter A) applied. AdataSourcebinding's ownlimitgets the positivity check objectui#10014 gave the saved view's operand. A refused binding cap (0, a negative, a fraction, or a non-number) is not authored: the chain falls through tosavedViewLimit(view), and when that is absent too, to the consumer's own default. The rule is objectui#10009's precedent (a contract-refused value is not authored, so the other source wins), applied to the two operands of one resolver. The defect carried on the thread (5814132982) is in the same stroke:ViewDataProviderno longer reports a refused view cap when the binding's own usablelimitis the cap actually used.What changed
composeElementDataSource:config.limit ?? savedViewLimit(view)becomesbindingLimit(config) ?? savedViewLimit(view).bindingLimitis module-private and applies the sameisUsableRowLimitpredicatesavedViewLimituses.nullstays absent, as??made it.elementDataSourceRefusedLimitMessage(the channel objectui#10014 built; ⛔ no third channel): it takes two optional trailing parameters,bindingandoperand('view', the default, or'binding'). The binding operand has its own sentence, built by a module-private sibling, and that sentence names the binding (dataSource binding on OBJECT ... declares limit: VALUE). So the two refusals are told apart when both fire. For the view operand, the builder now applies the condition "the binding supplied no usable cap" itself, so both callers share one copy of it. No new export:@object-ui/core's entry list is byte-identical, and a three-argument call answers exactly as before.ViewDataProvider.resolveElementDataSourceasks both operands and warns once per refused operand on the existingconsole.warnchannel.ElementDataSourceGate(renderer path):fromViewfor the row cap is now "the binding has no usablelimit".Zone 2 readings (measured on
ea02938cd, the branch base)element-data-source.tsheldconst limit = config.limit ?? savedViewLimit(view);incomposeElementDataSource,savedViewLimitreadingsavedViewRawLimitthroughisUsableRowLimit, andelementDataSourceRefusedLimitMessage(view, viewName, object). Confirmed.The callers that reported the view refusal:
ViewDataProvider.resolveElementDataSource, which calledelementDataSourceRefusedLimitMessage(view, config.view, config.object)and warned unconditionally;describeRefusedViewRowLimit, which returnednullonconfig.limit != nullor a usable component cap, underif (limit).Confirmed. A third caller of the composer exists:
useElementDataSource, whose consumers are the gate andelement:record_picker. The record picker reports neither refusal today, so by the dispatch's rule it is out of scope. It is listed under Acceptance notes.The builder was reused through a parameter, with no new export. Confirmed feasible. The additive parameters sit on an export that has not been released yet:
.changeset/9928-saved-view-limit-non-positive.mdis still pending.The truth table follows.
Truth table, measured before and after on both callers
The instrument was a throwaway probe, not committed. It ran against the base blobs of the three source files, then against HEAD, and the restore was proven by blob hash. "none" = no
limitreached the fetcher, or no cap was written to the node. Warnings are listed by the operand they name.ViewDataProvider(fetcherlimit):ElementDataSourceGate(pagination.pageSizewritten; component cap C):The probe covered 27 gate rows (C x binding x view, each three-valued). The 18 rows without a refused binding are identical before and after, in value and in warnings. On the gate, the carried defect's row (usable 3 x refused 0) was already silent, and it still is. Only the rows with a refused binding change resolution. Everywhere else, the one change is the carried-defect warning on
ViewDataProvider. This matches Zone 2 item 4.Tests (HEAD
22feffeb2)element-data-source.bindingLimitNotAuthored-10016.test.ts. It covers the composer's full table over refused values0,-10,25.5,"20", and the builder, which names each operand, stays silent under a usable binding, and gives two distinct messages when both operands are refused. It also covers everyViewDataProviderrow, including the dispatch's five minimum pins.ElementDataSourceGate.test.tsxhas every refused-binding row, the component-cap rows, the flatlimitkey, a re-render guard, the carried-defect row, and CONTROL and SILENCE rows.pnpm exec vitest run packages/core/src/data-scope/ packages/react/src/element-data-source/: 8 files, 260 tests passed.22feffeb2:pnpm exec vitest runover the two directories above, plus every other test file that names the binding, the gate, the hook, the composer orresolveElementDataSource(51 files across plugin-list, grid, kanban, timeline, detail, form, dashboard, map, calendar, gantt, charts, view, components, app-shell, console, types and scripts). The union also includes the two tests that read the touched sources as TEXT:injected-component-input-6950.test.tsandregistry-assertion-ratchet-8316.test.ts.useResolvedDataSource.schemaRendererContextRead-7209.test.tsis already inside the gate directory. Result: 61 files, 1203 tests passed.pnpm --filter @object-ui/core --filter @object-ui/react run type-checkpassed. Both echoedtype-check: Done, afterpnpm --filter '@object-ui/react^...' run buildrebuilt the closure from HEAD; the rebuilt core.d.tscarries the newoperandparameter.tsc -p tsconfig.test.json --listFilescounts the new core test file (1) and the gate test file (1).Ablations (each through
ablation-replace.mjsWRAP mode: the anchor had to hit x1, the landing was proven by anchor and replacement counts and a blob change, and the restore was proven by blob == HEAD and an emptygit diff HEAD)@object-ui/coreis aliased topackages/core/srcby the root vitest config, and nodist/was on disk during these runs. So each mutation reached both suites without a build.bindingLimitreturnsconfig.limit). Blobcc2aa376went to302d2fdeand was restored tocc2aa376. 35 failed / 225 passed: every refused-binding row on the composer,ViewDataProviderand the gate. 0 of the failures carry CONTROL or SILENCE. The two gate rows with a usable component cap stayed green, because the gate's ownfromViewcondition guards them, hence (c).if (isUsableRowLimit(binding?.limit)) return null;was deleted). Blobcc2aa376went tob7c7c10aand was restored. 5 failed: theViewDataProvidercarried-defect row, the builder row, the gate's carried-defect row, and two objectui#10241 pins whose guard now lives in the builder. Those two are "the binding's ownlimitwins over the view" and "a bindinglimitdisplaced a refused component cap".22feffeb2).fromViewwas reverted to presence (binding.config?.limit === undefined). Blobf2082ce4went toc9239886and was restored. Exactly 1 failed: "binding 0 + usable view cap + usable COMPONENT cap ⇒ the component's cap wins over the view baseline".Gates (each exit code captured before any pipe)
pnpm check:control-bytes:OK (scanned 8436 tracked text file(s); skipped 85 binary).pnpm check:new-line-citations:VERDICT new-cross-file-line-citations: 0 new citation(s).node scripts/check-changeset-presence.mjs:6 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s).pnpm check:changeset-claimspassed; it flags nothing.pnpm check:pending-changeset-literalspassed.node scripts/check-changeset-no-major.mjspassed.pnpm check:test-path-rootsandpnpm check:element-data-source-declaration(13 gate-consuming files) passed..mdgets "File ignored because no matching configuration was supplied".--format jsonreturned 6 results with 0 errors and 27 warnings. The base blobs of the same files give 27 warnings too: 13 + 9 + 5 on the three pre-existing files that carry any, and 0 on the new file.eslint.config.jshas noprojectService, noparserOptions.projectand no TypeChecked preset), and no rule undereslint-rules/reads the filesystem. So this diff cannot move a verdict on an untouched file.check:doc-examplesandcheck:readme-exports. Reason:PREREQUISITE NOT MET, because 32 packages are unbuilt. The diff touches no@exampleblock (0 hits in the diff) and adds no export. The full farm and repo-widepnpm lintare CI's.Population, with a lit control (card item 3)
The instrument was a throwaway walk at
22feffeb2, overgit ls-files: 549 strict-JSON files and 257 JSON fences in.md/.mdx(changesets and CHANGELOGs excluded), plus a literal scan ofdataSource: { object: ... }object literals in source.limit(CONTROL: usable)The control is lit in both rows, so this zero is a reading, not a dark instrument. No authored binding in the tree carries a refused
limit. This agrees with the ruling's facet ②, which said the change is not urgent.Seat amendment (2026-09-24T21:40Z). Line 2 now reads
Clause-②: yes. The exportedelementDataSourceRefusedLimitMessagegains two optional parameters, which changes the@object-ui/coreexports-map surface, so a contract review is owed before enqueue (claim5822089217, amended). The@object-ui/reactchangeset level moved frompatchtominorat226994a2f.Acceptance notes
@objectstack/spec17.4.0,PageComponentSchema.safeParsewithdataSource.limit: 0givestoo_smallatdataSource.limit, andElementDataSourceSchemarefuses0/-10(too_small) and25.5/"20"(invalid_type). Whether every write surface runs that parse before persisting was not measured.element:record_picker(throughuseElementDataSource). It reports neither the view's refusal nor now the binding's, so by the dispatch's rule it is out of scope. Its resolution follows the ruling for free (composed?.limit ?? props.limit ?? 50). Its registrydescriptionstill states the precedence asdataSource.limit ?? limit ?? 50, which predates both objectui#9928 and this card. 承接者:无.content/docs/guide/data-source.mdsays "sortandlimitoverride the view's". That is still true for a usable bindinglimit, and it says nothing about a refused one. It is outside this claim's file surface. 承接者:无.ReportViewforwardsdataSource.limitraw as$top, but the report contract declares that keydataSource?: never(and the spec'sReportSchemarefuses it as an unrecognized key), so it is not this binding. It was noted and not pursued.dataSourcebinding still wins over both" now holds for a usable binding cap. This PR's changeset states that explicitly.Changeset:
.changeset/10016-binding-limit-not-authored.md.@object-ui/coreisminor, following objectui#10014's level for this class, and@object-ui/reactisminorsince head226994a2f(seat respin; it waspatch, following objectui#10009 and objectui#10241), because the gate's precedence changes on the refused-binding rows. The changeset states the behaviour change plainly.The implementing session is
https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC(domain:uiseat 1 dispatch,mode:subagent).Generated by Claude Code