Skip to content

refactor(core,react): home the non-grid row ceiling in core; the note takes the result (objectui#7508) - #10426

Merged
os-elon-musk merged 1 commit into
mainfrom
claude/issue-7508-row-ceiling-to-core
Sep 25, 2026
Merged

os-elon-musk merged 1 commit into
mainfrom
claude/issue-7508-row-ceiling-to-core

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #7508
Clause-②: yes — two packages' public API moves (maintainer ruling A′, comment 5529427804)

Follow-up to objectui#7210 (already completed), executing ruling A′ as recorded on objectui#7508.

Public surface, per package

@object-ui/core — added

name kind change
NON_GRID_ROW_CEILING const moved from @object-ui/react, value unchanged
applyNonGridRowCeiling function moved from @object-ui/react, behaviour unchanged
NonGridCeilingResult type moved from @object-ui/react, shape unchanged
nonGridRowCeilingQuery function returning { $top } new — the one place the probe row's + 1 is written

@object-ui/react

name change
NON_GRID_ROW_CEILING, applyNonGridRowCeiling, type NonGridCeilingResult kept, now export … from '@object-ui/core' — the same bindings (pinned by identity)
NON_GRID_ROW_CEILING_TOP deleted, not re-exported
NonGridRowCeilingNote reshaped: its only prop is result: NonGridCeilingResult. drawn, total, truncated and className are gone, no deprecated path
nonGridRowCeilingQuery not re-exported: the ruling puts the + 1 in one exported helper, and core is its home

Release status, measured: the npm registry's latest @object-ui/react tarball (17.6.0) contains none of the five names (positive control: extractWriteErrorMessage hits 5 files in the same tarball), and .changeset/7210-non-grid-row-ceiling.md is still pending. So the deletion and the prop reshape reach no released consumer.

What changed

  • packages/core/src/utils/non-grid-row-ceiling.ts (new, beside extract-records.ts) and one core barrel line. The ruling-a′ docblock (the 2,000 measurement table) moved with the constant.
  • packages/react/src/utils/nonGridRowCeiling.tsx keeps only the note. It prints result.rows.length and result.total and decides on result.truncated. It now carries shrink-0 itself: the only styling any caller passed was the gantt's shrink-0 class string, and that is a no-op outside a flex container.
  • packages/react/src/index.ts: the three names re-exported from core; the stale "OPEN maintainer decision" comment replaced.
  • ObjectCalendar, ObjectGantt, ObjectMap, ObjectTree: the helpers come from the @object-ui/core import block each file already had. Every $top site spreads nonGridRowCeilingQuery() (calendar 2, gantt 1, map 2, tree 2). The rowCeiling state holds the NonGridCeilingResult itself, or null on the host-data paths that used to reset it to truncated: false. The note is mounted with result={rowCeiling} only while a result is held. What they fetch, draw and print is unchanged.
  • Tests: the helper pins and contractEnvelope-6839 moved to packages/core/src/utils/__tests__/, the react test now pins only the note and the re-export, and 11 plugin test files moved off the deleted constant onto nonGridRowCeilingQuery().$top.
  • packages/react/README.md "NON_GRID_ROW_CEILING" section rewritten to the new shape (heading kept). .changeset/7508-non-grid-ceiling-to-core.md: minor on core and react, with every moved, new, removed and reshaped name.

The ruling's two pins

  1. The four renderers agree on one ceiling and one + 1. Every renderer test that reads the wire (rowCeiling-7210, inlineQueryKeys-*, elementDataSource, hostDataProp-7210) asserts $top === nonGridRowCeilingQuery().$top, and each row-ceiling pin asserts the note names NON_GRID_ROW_CEILING. Core pins the query as exactly { $top: NON_GRID_ROW_CEILING + 1 }, holding only $top, and a fresh object on every call.
  2. The note cannot render a drawn/total pair that did not come from a result. Four @ts-expect-error lines, one each for drawn, total, truncated and a missing result, are compiled by react's tsc -p tsconfig.test.json (--listFiles confirms the file is in that program). A runtime case forces the loose props past the compiler and shows they are ignored. A third case uses a truncated result of 7 rows and shows the note prints 7, not the constant.

Verification — all runs at ae0a5b7

  • Build closure: @object-ui/react..., @object-ui/core..., the four plugins' ^... closures, then the check-doc-snippet-types --build-filter closure (turbo Tasks: 35 successful, 35 total), then console — all exit 0.
  • Tests (repo-root vitest, --maxWorkers=2): packages/core/ 168 files / 3509 passed; packages/react/ 94 / 1291; plugin-calendar + plugin-map + plugin-tree 85 / 556; plugin-gantt 72 / 558. The 35 test files outside those six packages that name a changed file (types, i18n, components, plugin-list, plugin-grid, plugin-view, apps/console, examples/schema-catalog, scripts/tests) also pass: 35 / 1852.
  • type-check (the tsc --noEmit && tsc -p tsconfig.test.json pair) for core, react, calendar, gantt, map and tree: all six Done. lint for the same six: 0 errors. The only warnings on the new core module are the 4 no-explicit-any, moved verbatim from the react module.
  • Gate verdict lines:
    • check-changeset-presence: "23 source file(s) of 6 released package(s) changed, and this change declares 1 changeset(s)".
    • check-changeset-no-major: "No changeset declares a major bump". check-changeset-fixed ✅. check-changeset-overwrite: "No pre-existing changeset was modified or deleted".
    • check:changeset-claims: report-only, exit 0. It lists 11 pending bodies that name a touched file; I re-read each and none describes the ceiling.
    • check:readme-exports ✅: "544 real, 0 wrong-path, 0 fabricated"; the react README's two self-imports are judged real.
    • check-doc-snippet-types: "673 of 673 block(s) judged, 0 failed". check-doc-example-types ✅. check:doc-types ✅. check-doc-links: "Links are valid across 17 scan roots".
    • check:self-import, check:phantom-deps, check:unused-deps, check:unreferenced-sources, check:esm-specifiers ✅. check:node-esm-load: 34 of 39 entries imported and evaluated; the 5 exclusions are by design.
    • check:new-line-citations: "0 new citation(s)". check:control-bytes ✅.
    • check:sdui-registration-pins: "All 16 registration(s) … present". check:eager-closure ✅ (headroom 61.2 KB). check:side-effects-array ✅.
    • check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape ✅. check-type-check-coverage 43/43. check-lint-coverage 46/46.
    • check:i18n-keys ✅. check:i18n-dead-keys exit 0 (report-only). check:pending-changeset-literals exit 0. check:entry-guard ✅.
    • check-governed-queue-guard --test over the 25 changed paths: NOT GOVERNED.

Reverse verification / ablation

These ran on the committed tree through ablation-replace.mjs. Each run confirmed the anchor count went down and the target text count went up before any test ran. Each restore is proven: blob == HEAD and git diff HEAD empty.

  1. nonGridRowCeilingQuery drops the + 1. Predicted: the core pins go red and the renderer pins stay green. Observed: 8 failed / 11 passed. The 4 core pins and the envelope pin went red. So did 2 gantt row-ceiling cases and the tree row-ceiling case: those fakes honour $top, so exactly 2,000 rows came back and nothing was truncated. That is more red than predicted, and it means the renderer pins also depend on the one + 1.
  2. The note prints the constant instead of result.rows.length: 2 failed / 5 passed (the two result-count cases), as predicted.
  3. The note's prop type re-admits drawn: react tsc -p tsconfig.test.json exit 2 with TS2578: Unused '@ts-expect-error' directive on the drawn line, as predicted.
  4. ObjectTree's object arm writes $top: 2000 instead of the spread: tree row-ceiling pin 1 failed / 1 passed, as predicted.
  5. The README snippet reverts to the three-prop call: check-doc-snippet-types reports "673 of 673 block(s) judged, 1 failed" with TS2322 on packages/react/README.md (drawn does not exist on the note's props).

Acceptance notes

  • Three edits outside the claimed surface, all required by the move:
    • (a) one comment bullet in packages/core/src/utils/extract-records.ts now points at the moved call site. The move made the old path false.
    • (b) the react helper test and contractEnvelope-6839 moved into core's __tests__ together with the module they test.
    • (c) seven plugin test files beyond the row-ceiling ones imported the deleted constant: ObjectCalendar.inlineQueryKeys-9061, ObjectGantt.elementDataSource, ObjectGantt.hostDataProp-7210, ObjectGantt.inlineQueryKeys-8769, ObjectMap.elementDataSource, ObjectMap.inlineQueryKeys-9061, ObjectTree.inlineQueryKeys-9136.
  • Pending .changeset/7210-non-grid-row-ceiling.md still names NON_GRID_ROW_CEILING_TOP. It lists it as a new react export and says the fetches ask for it. I did not edit it: it is outside the claim, and check-changeset-overwrite watches that file. This PR's changeset says outright that it supersedes that export list. Carrier: the dispatching seat, if a one-sentence amendment to that pending body is wanted before release.
  • className was removed from NonGridRowCeilingNote. I read the ruling's "nothing else" literally. Flagged for the contract review.
  • content/docs/guide/data-source.md still describes NON_GRID_ROW_CEILING as a @object-ui/react export. That is still true through the re-export, so I left it.
  • packages/plugin-markdown/src/toc.ts uses ### NON_GRID_ROW_CEILING only as a slugging example, and its parity test works on a literal string. The react README heading it mirrors is kept, so nothing moves.
  • Serial: objectui#7750 is queued behind this PR on the core barrel. I did not touch packages/core/src/data-scope/.
  • Deviation: commit ae0a5b7 carries a model-named Co-Authored-By trailer. I did not rewrite it, because this repo bans force-push.

Generated by Claude Code

…es the result (objectui#7508)

Ruling A′: NON_GRID_ROW_CEILING, applyNonGridRowCeiling and
NonGridCeilingResult move to @object-ui/core beside extractRecords, and
@object-ui/react re-exports them. NON_GRID_ROW_CEILING_TOP is deleted; the
probe row's +1 lives only in core's new nonGridRowCeilingQuery(). The four
non-grid renderers spread that query into their find() and hand
NonGridRowCeilingNote the result, which is now its only prop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: core package: react plugin tests labels Sep 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 11 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6475-gantt-block-face-declared.md

  • names plugin-gantt/src/ObjectGantt.tsx → packages/plugin-gantt/src/ObjectGantt.tsx — edited by this change

    The gantt nested-block spelling of a gantt config ({ type: 'object-gantt', gantt: { … } }) had no mirror entry at all: it rode through BaseSchema's .passthrough() entirely unvalidated, and the published TypeScript never taught the shape either — an author writing gantt: { … } got no completion, no type checking, no error on a misspelt member. It was the 28th and last of the keys getGanttConfig (plugin-gantt/src/ObjectGantt.tsx) reads off the schema that objectui#6051 (PR feat(types,plugin-gantt): declare the flattened GanttConfig face ObjectGantt reads (#6051) #6472) did not declare — severed into its own card because, unlike the other 27, declaring it changes what a published tool refuses.

.changeset/6939-objectql-record-source-refinement.md

  • names plugin-map/src/ObjectMap.tsx → packages/plugin-map/src/ObjectMap.tsx — edited by this change

    Both renderers resolve their records from one of three keys, in this order — getDataConfig in plugin-map/src/ObjectMap.tsx and plugin-gantt/src/ObjectGantt.tsx: data, then staticData, then objectName. Both mirrors required objectName alone, so a document authored on staticData drew correctly and was refused by safeValidateSchema — six catalog entries, three per component.

  • names plugin-gantt/src/ObjectGantt.tsx → packages/plugin-gantt/src/ObjectGantt.tsx — edited by this change

    Both renderers resolve their records from one of three keys, in this order — getDataConfig in plugin-map/src/ObjectMap.tsx and plugin-gantt/src/ObjectGantt.tsx: data, then staticData, then objectName. Both mirrors required objectName alone, so a document authored on staticData drew correctly and was refused by safeValidateSchema — six catalog entries, three per component.

.changeset/7313-object-calendar-record-source.md

  • names plugin-calendar/src/ObjectCalendar.tsx → packages/plugin-calendar/src/ObjectCalendar.tsx — edited by this change

    ObjectCalendar resolves its records through the shared ladder (resolveRecordSourceConfig in @object-ui/core, called from plugin-calendar/src/ObjectCalendar.tsx): data first, then staticData, then objectName. The published TypeScript interface REQUIRED objectName and declared neither data nor staticData; the published Zod mirror did the same. So an object-calendar node authored on staticData — the route the plugin page documents twice — rendered correctly and was refused by safeValidateSchema, and could not be annotated with its own type (TS2741: Property 'objectName' is missing).

.changeset/7712-kanban-calendar-filter-input.md

  • names ObjectCalendar.tsx → packages/plugin-calendar/src/ObjectCalendar.tsx — edited by this change

    ObjectKanban.tsx sends the authored key to the query as $filter: schema.filter and ObjectCalendar.tsx does the same, and @objectstack/spec's ComponentPropsMap declares filter on both blocks (measured: safeParse accepts it, and refuses an undeclared key by name on the same call). But none of the four registrations that publish those two renderers listed filter in inputs, and sdui-parser's validateTree reports unknown-prop for every key no inputs entry claims. So an author writing the one spelling that WORKS was told it was unknown — objectui#6678's shape, where a correct write draws the same diagnostic as a write that does nothing. That is worse than an inert key: it actively punishes the correct behaviour, and the honest response to it is to delete working metadata.

.changeset/7912-schema-renderer-datasource-contract.md

  • names packages/react/README.md → packages/react/README.md — edited by this change

    Five as any reads of this context in @object-ui/fields are gone — they were redundant the moment the seam became honest — and LookupField's local re-declaration of the imported context as a Context of any, which laundered its dataSource read while looking typed, is gone with them. Both directions of the contract are pinned against the real compiler in SchemaRendererContext.dataSourceType.pin.test.ts, and the card's planted documentation probe (a bare string in packages/react/README.md's provider example) now fails pnpm check:doc-snippets, where it used to exit 0 with zero diagnostics.

.changeset/8171-calendar-sort-input.md

  • names ObjectCalendar.tsx → packages/plugin-calendar/src/ObjectCalendar.tsx — edited by this change

    objectui#7712's defect, one key over. ObjectCalendar.tsx lowers the authored key onto its own query as $orderby: convertSortToQueryParams(schema.sort), and @objectstack/spec's ComponentPropsMap['object-calendar'] declares sort (measured on 17.2.0: safeParse({ objectName, sort }) returns success: true, while the same strict schema on the same call refuses bogusProp by name — that control is what makes the acceptance a verdict). But neither of the two registrations that publish this renderer — plugin-calendar:object-calendar and view:calendar — listed sort in inputs, and sdui-parser's validateTree reports unknown-prop for every key no inputs entry claims. So an author writing the one spelling that WORKS was told it was unknown — objectui#6678's shape, where a correct write draws the same diagnostic as a write that does nothing.

.changeset/8174-kanban-calendar-filter-sort.md

  • names ObjectCalendar.tsx → packages/plugin-calendar/src/ObjectCalendar.tsx — edited by this change

    filter had four declaration faces and only three of them named it: @objectstack/spec declares it (ComponentPropsMap['object-kanban'] and ['object-calendar']), both plugins' registration inputs publish it, and both renderers read it — ObjectKanban.tsx lowers schema.filter onto $filter, ObjectCalendar.tsx lowers schema.filter onto $filter and schema.sort onto $orderby through convertSortToQueryParams. This package's own published faces (the TypeScript interface and its zod mirror) named none of them, so an authored value reached the renderer only through BaseSchema's index signature and the mirror's .passthrough() — admitted, never examined. That is the same reasoning finding(types,plugin-kanban): ObjectKanbanSchema requires groupField (zero read sites) and declares neither groupBy nor limit — no working object-kanban node is assignable to any declared type #7322 used to move groupBy and limit into this same interface.

.changeset/8253-export-tree-view-config.md

  • names plugin-tree/src/ObjectTree.tsx → packages/plugin-tree/src/ObjectTree.tsx — edited by this change

    What was wrong. tree is a host-composition-only view type — ruled deliberate on objectui#5321, it is a member of neither ObjectViewSchema.defaultViewType nor NamedListView.type, so the branch runs only when a host passes a views prop. On that path a per-view tree block is read at four sites, and its only description anywhere was a module-local, non-exported interface TreeConfig inside plugin-tree/src/ObjectTree.tsx. The live host is the console: it stores view records and passes them as views, and its create-view dialog offers tree. So a real consumer wrote this block with no type to write it against, and a misspelled parentFeild was admitted by the views entry's [key: string]: any, stored, read by nobody and reported by nothing. Declared ≠ enforced on a surface a non-author re-writes.

.changeset/8284-content-channel-per-component.md

  • names packages/react/README.md → packages/react/README.md — edited by this change

    Migration. Nothing that renders today stops rendering: a document authoring the channel its renderer reads is unchanged, and a document authoring the other one rendered an empty element before and is now refused instead. The repo-wide census found five documents in this state — packages/react/README.md, content/docs/guide/expressions.md, two blocks in content/docs/guide/schema-rendering.md and packages/components/TESTING.md — every one of them a form or container authoring body; all five are corrected in this change. If your own metadata authors the refused channel on one of these twelve node types, the component was already drawing nothing there; rename the key to the one in the table.

.changeset/8466-calendar-color-allday-fields.md

  • names ObjectCalendar.tsx → packages/plugin-calendar/src/ObjectCalendar.tsx — edited by this change

    ObjectCalendar.tsx's getCalendarConfig reads FIVE flat keys off the node, and packages/plugin-calendar/README.md teaches all five in one sentence — "point titleField / startDateField / endDateField / allDayField / colorField at your own fields when they differ." Only three of the five were declared. The other two reached the renderer through BaseSchema's [key: string]: any on the TypeScript face and its .passthrough() on the zod mirror: admitted, never examined. A misspelling therefore left the calendar silently colourless while every published gate passed.

.changeset/9308-data-root-unbound-from-adapter.md

  • names packages/react/README.md → packages/react/README.md — edited by this change

    The Data Context passages of content/docs/guide/schema-rendering.md and packages/react/README.md teach the scope channel accordingly.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with f475557c4 (merge-base with origin/main): 24 file(s) changed outside .changeset/, read against 1368 pending declaration(s) that publish a body (1943 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3043.7 KB 3104.5 KB
Main entry chunk (gzip) 148.4 KB 350 KB
Entry file index-B8B8chOP.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 542.47KB 129.64KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 224.12KB 62.26KB
fields (index.js) 255.56KB 64.73KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.22KB 14.56KB
plugin-charts (index.js) 72.54KB 20.26KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.45KB 35.33KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 260.94KB 67.93KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.47KB
plugin-gantt (index.js) 168.25KB 41.46KB
plugin-grid (index.js) 215.22KB 58.85KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.36KB 28.24KB
plugin-map (index.js) 21.83KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.72KB 8.96KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 85.86KB 21.38KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ae0a5b7ff456fa3fde2a40d7d496afbc0697c408

Inputs read: card 7508 body + 4 comments (ruling 5529427804, retriage 5737038551, Claim 5824018182, os-dev-report 5824558104 — the last two treated as the seat's own words), PR 10426 body + 25-file list, git diff a707197..ae0a5b7 (1585 lines, 25 files, every hunk read), main-side packages/react/src/utils/nonGridRowCeiling.tsx, both changesets, .changeset/config.json, objectui AGENTS.md (main), scripts/check-changeset-overwrite.mjs header, npm tarballs @object-ui/react@17.6.0 and @object-ui/core@17.6.0, check-runs on the head.

① Derived judgments

Core home — RIGHT. New packages/core/src/utils/non-grid-row-ceiling.ts (161 lines) sits beside extract-records.ts; packages/core/src/index.ts adds export * from './utils/non-grid-row-ceiling.js' on the line after the extract-records.js export. It exports NON_GRID_ROW_CEILING = 2000, nonGridRowCeilingQuery(): { $top: number }, interface NonGridCeilingResult (generic, default any), applyNonGridRowCeiling. Bodies compared against main's react module (lines 83, 100-136): byte-equivalent; the only import is ./extract-records.js, so core gains no React/i18n dependency.

React re-export — RIGHT, identity holds. packages/react/src/index.ts:114-116: export { NON_GRID_ROW_CEILING, applyNonGridRowCeiling } from '@object-ui/core', export type { NonGridCeilingResult } from '@object-ui/core', export { NonGridRowCeilingNote } from './utils/nonGridRowCeiling.js'. No export * from core (count 0). Pinned by nonGridRowCeiling.test.tsx (entry.X toBe core.X for both value names). Holds in the published artifact too: react's dist is a plain tsc emit (17.6.0 dist/index.js keeps from '@object-ui/i18n' external, 0 inlined I18nProvider), so the re-export line ships as a live binding, not a copy. Observation: nonGridRowCeilingQuery is NOT re-exported by react. The ruling says react keeps "the names" (the three that moved), so this matches the exact words, but it is asymmetric — react re-exports the slicing half (applyNonGridRowCeiling) while a react consumer must go to core for the query half; the react README snippet now imports from two packages. Not a breach.

_TOP deleted, +1 in one place — RIGHT. git grep NON_GRID_ROW_CEILING_TOP refs/review/pr-10426 = 7 hits: 2 in pending .changeset/7210-non-grid-row-ceiling.md, 1 in .changeset/7508-non-grid-ceiling-to-core.md (the "removed" line), 4 in packages/react/src/utils/nonGridRowCeiling.test.tsx (docblock + negative assertions that the name is absent from both entries). 0 in any packages/*/src non-test file. Literal 2001 in non-test src: 0. NON_GRID_ROW_CEILING + 1: 3 hits — non-grid-row-ceiling.ts:117 (the query body) and 2 assertions in core's own pin. All 7 renderer $top sites spread ...nonGridRowCeilingQuery() (calendar 654, 733; gantt 874; map 840, 921; tree 760, 842). The only other $top literal in the four files is gantt's $top: 1000 ref-option-label fetch, present on main at line 1433, unrelated.

NonGridRowCeilingNote shape — RIGHT against the exact words. Props type is { result: NonGridCeilingResult } only; prints result.rows.length and result.total, decides on result.truncated; no deprecated path. Pins: 4 @ts-expect-error lines (drawn, total, truncated, missing result) compiled by react's tsc -p tsconfig.test.json; a runtime case forcing loose props shows they are ignored; a 7-row truncated result prints 7, not 2000.

className — judged: the literal reading is defensible and precedented; not a §5 #3 breach; the affordance loss is real but additive to restore. Evidence for over-reading: the ruling glosses "and nothing else" with "the three scattered props go"; the card body's option A and its audit table name only drawn/total/truncated; className carries no data and is orthogonal to the coherence defect the reshape fixes. Evidence for the literal reading: the words say "nothing else"; AGENTS §5 #3 says expose className "in schema props" and the note is not a schema node (registry hits for it: 0; consumers: the 4 renderers and the README only); the precedent the note's own docblock cites, ChartFootnote at packages/plugin-charts/src/AdvancedChartImpl.tsx:591, takes { note, children } with no className and hardcodes shrink-0 — the same construction; the only className ever passed was gantt's full string, which the note now carries verbatim. With 0 published consumers (see ②) and className? being a minor-level add if the maintainer wants it back, nothing is lost irreversibly. Dev flagged it correctly.

Behaviour on the four renderers — RIGHT. State becomes NonGridCeilingResult | null (was { truncated; total? } initialised { truncated: false }). Fetch paths setRowCeiling(capped); host-data paths setRowCeiling(null) — calendar externalData effect (566), gantt rest.data array (799), map dataProp array (789), tree passed array (788). setRowCeiling counts main=head per file (3/2/3/3); setData/setRecords counts unchanged (10/11/5/5). The only reader of rowCeiling in each file is the mount {rowCeiling && NonGridRowCeilingNote result={rowCeiling}} (grep: 1 line per file), so the null state is never dereferenced. The note renders null when !result.truncated, so the footnote appears under exactly the prior condition, and when truncated rows.length is always the ceiling (the slice), so the printed numbers are identical. DOM class: gantt unchanged (it passed exactly the string now hardcoded); calendar/map/tree gain shrink-0 — map root min-w-0 overflow-hidden and tree root w-full overflow-auto are not flex so it is inert; calendar root is className={className} from the host, inert unless the host makes it a flex column, where it does what gantt's did. Renderer pins (rowCeiling-7210 x4, inlineQueryKeys x4, elementDataSource x2, hostDataProp) assert $top === nonGridRowCeilingQuery().$top and the note text.

Extra files — all necessary, nothing unrelated. Diff file count 25 = PR file list 25. Beyond the dispatch's named surface: extract-records.ts one comment bullet whose path became false; contractEnvelope-6839 test renamed into core __tests__ (87% similar; import path + a PROBE_TOP alias only); 7 plugin tests that imported the deleted constant — each hunk is the import swap, nonGridRowCeilingQuery().$top substitution, and docblock wording. Core README untouched: 0 mentions of the 4 core exports (it also has 0 mentions of extractRecords, so it is not an export index — consistent with practice, but §5 #2 is met only on the react side). content/docs/guide/data-source.md:331 still cites NON_GRID_ROW_CEILING from @object-ui/react — true through the re-export. The 4 any in the core module are verbatim moves (lint warnings only).

② Semver level

minor is the right declaration. AGENTS.md 版本号策略 (lines 257-263): never declare major; "objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)"; enforced by check-changeset-no-major (CI "Changeset Bump Policy" success). .changeset/config.json puts core, react and the 4 plugins in one fixed group of 39. The ruling itself says "Changeset: minor on core and react with the moved names listed" — met.

Changeset prose is true. Moved names/value unchanged (byte-compare), same bindings (re-export + pin), _TOP removed with a migration line, breaking prop change names drawn/total/truncated/className and the carried shrink-0, "what they fetch, draw and print is unchanged" (true; three notes gain an inert class). The 4 plugin packages are absent from the frontmatter; check-changeset-presence needs only one changeset ("23 source files of 6 released packages, 1 changeset") and the fixed group bumps them regardless, so their CHANGELOGs carry no entry for an import-source swap — acceptable.

Never published — confirmed independently. npm pack @object-ui/react@latest @object-ui/core@latest → both 17.6.0. React tarball: 0 files for each of NON_GRID_ROW_CEILING_TOP, NON_GRID_ROW_CEILING, applyNonGridRowCeiling, NonGridRowCeilingNote, NonGridCeilingResult, nonGridRowCeilingQuery; positive control extractWriteErrorMessage 5 files. Core tarball: 0 for all; positive control extractRecords 3 files. .changeset/7210-non-grid-row-ceiling.md is present and unmodified at head. So the deletion and reshape reach no released consumer.

Relation to pending 7210 — stale, and the stated reason for leaving it is weaker than claimed. 7210's body (lines 20, 47) says the four fetches "ask for NON_GRID_ROW_CEILING_TOP" and lists it as a new react export; both will be false in the release that consumes both files. The PR says it was left because check-changeset-overwrite watches the file, but that script's header says it is REPORT-ONLY by default (OS_CHANGESET_OVERWRITE_ENFORCE=1 to block) and that prose corrections to a pending changeset were 19-for-19 legitimate in history — so a one-sentence amendment is permitted and is the correct fix. The 7508 body's "This supersedes the export list in objectui#7210's entry" is a true mitigating statement. Non-blocking; carrier: dispatching seat, before release.

③ Boundary flags

  • CI on the head at read time (/commits/ae0a5b7…/check-runs, total 42): 27 success, 3 skipped, 11 in_progress (Lint, Type Check, Build Docs, Spec Main Shape Gate, Test shards 1 and 3-8), 1 queued (Test shard 2). Changeset Bump Policy / Declaration / Fixed Group / Overwrite Report, README Export Check, Doc Snippet Type Check, Test (dist pins), Build & E2E, Line Citation Gate: success. Landing must wait for the 12 unconcluded runs; that is a gate reading, not a contract finding.
  • PR is a draft; labels are documentation / package: core / package: react / plugin / tests. The ruling's needs:contract-review carrier label is absent (the dev reports zero label writes, so this is the dispatching seat's).
  • Commit ae0a5b7 trailer reads Co-Authored-By: Claude Opus 5.5 — model-named, dev-declared; the squash message is the seat's to set.
  • Declared deviations (3) match the diff exactly; open_questions is empty and nothing in the diff contradicts that. The className removal and the react-side non-re-export of nonGridRowCeilingQuery are the two judgment calls, both consistent with the ruling's exact words and both flagged by the dev.
  • Core README carries no line for its 4 new public names (react README documents them under its kept ### NON_GRID_ROW_CEILING heading). Not a ruling requirement; a follow-up if the maintainer wants core's README to name them.

Implemented-by: claude/issue-7508-row-ceiling-to-core
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS

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

Labels

Projects

None yet

2 participants