Skip to content

fix(non-grid,i18n,react): correct the row ceiling changeset, its pins and their rationale comments - #7517

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-7507-7391-correctives
Sep 3, 2026
Merged

fix(non-grid,i18n,react): correct the row ceiling changeset, its pins and their rationale comments#7517
os-project-manager merged 2 commits into
mainfrom
claude/issue-7507-7391-correctives

Conversation

@claude

@claude claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #7507

Six of the seven post-merge correctives the Clause-② audit of #7391 found (audit comment 5527977938). The seventh — the five symbols published on @object-ui/react's sole entry — is a maintainer decision on #7508 and is not touched here: this branch adds, moves, renames and retires zero exported symbols. Clause-② remains no.

Verified at a221c9288: 170 test files / 1687 tests green (plugin-calendar, plugin-map, plugin-gantt, plugin-tree, i18n, and the react ceiling unit), plus type-check green for all six touched packages via turbo (21 tasks, deps built first).


What was re-measured, and what it said

Nothing below was taken on the audit's say-so. Every claim was re-derived on this branch first; where an ablation is quoted, the mutation was proven on disk by grepping for the injected marker and the removed text, and the restore was proven byte-identical against the HEAD blob hash. Vitest resolves every @object-ui/* specifier to that package's src through the root config's alias table, so these ablations need no rebuild — there is no dist in the resolution path to go stale.

Baseline, before any change: 5 files / 16 tests green.

1. Changeset level and copy — .changeset/7210-non-grid-row-ceiling.md

  • The four view packages move patchminor. They carry the behaviour break (a result set above 2000 rows is no longer fully drawn); the fixed group makes the released version identical either way, but the per-package CHANGELOG would have filed a break under "Patch Changes".

  • The example footnote is replaced with the copy that actually renders. Measured, not copied: a throwaway probe rendered NonGridRowCeilingNote three ways and asserted a deliberately wrong string so the run could not pass silently — 3 failed, 3 printed values:

    PROBE_NO_PROVIDER = [Showing the first 2000 of 41234 records. Narrow the filter.]
    PROBE_PROVIDER    = [Showing the first 2000 of 41234 records. Narrow the filter.]
    PROBE_UNKNOWN     = [Showing the first 2000 records. Narrow the filter.]
    

    (delimiters shown as brackets; the probe printed them as angle runs.)

    No separators on either path. The i18next instance declares no format (packages/i18n/src/i18n.ts) and the provider-less fallback interpolates through String(v) (fallbackInterpolation.ts). The probe file was deleted.

  • NonGridCeilingResult joins the export list. It is already exported (packages/react/src/index.ts, export type { NonGridCeilingResult }) — listing it is documentation of a published symbol, not a new export.

2. The map and calendar pins now grade the cap they are named for

Audit finding reproduced first. With setData(capped.rows) replaced by setData((result as any).data ?? capped.rows) in both components, ObjectMap.rowCeiling-7210 and ObjectCalendar.rowCeiling-7210 stayed green, 4/4, drawing 2001 rows. They pinned $top and the footnote and never "draws at most N".

  • CalendarCalendarView is stubbed the way the gantt pin stubs GanttView (and the way ObjectCalendar.unscheduled-7071 already stubs it, via importOriginal), exposing data-event-count. The above-ceiling case asserts it equals NON_GRID_ROW_CEILING; the below-ceiling case asserts 12, so the pair cannot be satisfied by a view that caps everything unconditionally.
  • Map — a case of its own, because clustering is the map's own defence and it folds the count away: above 100 markers the DOM holds bubbles, not markers. enableClustering={false} makes the count observable without changing what reached the view (clustering is a pure function of the marker array), and the DOM marker count is then literally the ruling's "the DOM row count equals the ceiling". The below-ceiling case asserts 20 markers with no prop at all — 20 is under the map's own clustering threshold.

Ablation, prediction stated before the run: under the same uncapped mutation both pins go red at the new count assertion, and the new externalDataCeiling-7507 pin goes red at its count waitFor. Measured: exactly that — 3 failed / 4 passed, red at ObjectCalendar.rowCeiling-7210.test.tsx:124, ObjectMap.rowCeiling-7210.test.tsx:125, ObjectCalendar.externalDataCeiling-7507.test.tsx:120, each 2001 against 2000. Restored byte-identically; the pins are green again.

3. Four docblocks stated a reverse-verification mechanism that does not happen

Measured by deleting $top: NON_GRID_ROW_CEILING_TOP from each component in turn:

pin docblock predicted measured
gantt red "at the FOOTNOTE assertion" red at the $top assertion, 1 failed / 2 passed
tree red "at BOTH the row count and the footnote" red at the $top assertion only; the row count stays 2000
calendar red "at the footnote assertion" red at the $top assertion only
map red "at the footnote assertion" red at the $top assertion only

One mechanism explains all four: an adapter with no $top answers with the whole filtered set, so applyNonGridRowCeiling still sees more rows than the ceiling, still slices to it, and still reports truncated from the rows in hand. The drawn count stays 2000 and the note still names both numbers. Losing the $top is a bandwidth regression, not a correctness one — which is exactly why the $top assertion is not redundant with the footnote.

The calendar and map docblocks were re-measured after their new count assertions landed, as asked: the $top ablation is unchanged (still $top-only, at :115 and :94), so each of the four now records the measured mechanism, and calendar and map additionally record the second ablation their count assertions exist for.

4. Calendar external-data sync left rowCeiling stale

ObjectCalendar.tsx had five setData paths; four also reset rowCeiling, the external-data sync did not. A component that drew its own truncated fetch and was then handed a short data array kept a footnote describing rows no longer on screen. Latent today — ObjectView passes data from mount — and fixed with the one line the other four paths already carry.

Pinned in ObjectCalendar.externalDataCeiling-7507.test.tsx, with a live control: the first assertion proves the note really was raised by this component's own fetch, so its later absence is a state change and not a query that matched nothing.

Ablation, prediction stated before the run: remove the reset and the new pin goes red at expect(screen.queryByRole('note')).toBeNull(), while rowCeiling-7210 stays green. Measured: exactly that — 1 failed / 3 passed, red at ObjectCalendar.externalDataCeiling-7507.test.tsx:131.

5. The false dependency comment — packages/react/src/index.ts

"the only package all four already depend on" is measured false: packages/plugin-{gantt,calendar,map,tree}/package.json each also list @object-ui/core, @object-ui/components and @object-ui/types. (@object-ui/i18n is not common to all four — plugin-map does not depend on it.) The comment now states the real reason — the round's barrel fence over exactly those three, plus @object-ui/react having no subpath export — and cross-links #7508 so the next reader finds the open decision instead of a settled-sounding rationale. Comment text only; the export statements are untouched.

6. Stale headroom comment — packages/i18n/src/locales/*.ts (ten packs)

"~1 KB of headroom" named a budget line that no longer holds these bytes. Since #7399 the catalogues have their own chunk: i18n-locales, ceiling 455_000 against a recorded baseline of 446_0768,924 B, about sixty short keys' worth across ten locales at the ~147 gzipped bytes/key the gate's own docblock measures. (framework, which the old comment was implicitly about, is now 71_000 over 61_465.) Read live from PER_CHUNK_GZIP_CEILINGS / PER_CHUNK_BASELINE.

⚠️ Stated honestly in the comment as the ceiling's distance from the baseline it was measured from, not as today's build: three commits have added locale keys since that baseline was recorded, so the live figure is somewhat smaller. The comment points at pnpm check:eager-closure for the number in force. A fresh figure needs a full apps/console build, which is Bundle Analysis's job on this PR.


Verification

what result
vitest runplugin-calendar, plugin-map, plugin-gantt, plugin-tree, i18n, react ceiling unit, at a221c9288 170 files / 1687 tests passed
turbo run type-check — the six touched packages, --concurrency=2 (deps built first) 21 tasks successful, all six type-check tasks executed
check:control-bytes, check:vi-mock-specifiers, check:vi-mock-inherit, check:i18n-keys, check:i18n-drift, check:i18n-dead-keys all exit 0 at a221c9288
node scripts/check-changeset-presence.mjs ✅ 17 source files of 6 released packages changed, 1 changeset added
eslint --no-inline-config --format json over the changed set 17 files judged, 0 errors, 103 warnings, all @typescript-eslint/no-explicit-any in the fixtures' as any idiom (lint.yml sets no --max-warnings)

The lint narrowing is a measurement, not a skip. The population came from eslint's own config, not from a guess: its one files entry is **/*.{ts,tsx}, so 17 of the 19 changed files are in scope (the two .changeset/*.md are not), and --format json reported 17 results — every in-scope changed file judged. The config declares no parserOptions.project / projectService, so type-aware linting is not enabled: a file's verdict is a function of that file's own source and the shared config, neither of which this diff moves for any untouched file. pnpm lint runs the whole farm on this PR regardless.

One check is NOT MEASURED rather than green: check:readme-exports exits 1 on this tree with its type entry ./dist/index.d.ts is not on disk — run pnpm build first and a population COLLAPSED verdict. That is a prerequisite failure, not a finding: it needs every package built. It cannot go red on this diff either way — the gate judges README docs against the export surface, and this branch changes no export.

Fences observed

Changeset

.changeset/7507-non-grid-ceiling-correctives.md uses the empty-frontmatter exemption, deliberately: every corrective here lands inside a change that has not been released — .changeset/7210-non-grid-row-ceiling.md is still pending on main — so the calendar fix repairs a defect no user has seen, and the release note it belongs to is the #7210 changeset, corrected in place.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…e comments

Post-merge correctives to #7210's platform row ceiling (#7507), from the
Clause-② review that ran as an audit after the PR landed ungated.

- Changeset: the four view packages carry the behaviour break, so they move
  from `patch` to `minor`; the example footnote is replaced with the copy that
  actually renders (measured: no thousands separators on either the provider
  or the provider-less path); `NonGridCeilingResult` joins the export list.
- Map and calendar pins now grade the cap they are named for. Both were green
  under a mutation that drew 2,001 rows, because they asserted `$top` and the
  footnote and never the count that reached the view.
- `ObjectCalendar`'s external-`data` sync clears `rowCeiling`, the one
  `setData` path that did not. Latent today, pinned now.
- Comment-only: the false "the only package all four already depend on"
  rationale on `@object-ui/react`'s entry (all four also depend on `core`,
  `components` and `types` — the real reason was the round's barrel fence, and
  the home is an open decision on #7508), and the stale "~1 KB of headroom"
  note in the ten locale packs, which named a chunk that no longer holds them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
…ocblocks

All four predicted a mechanism that does not happen. Measured on this branch,
each ablation proven on disk and restored byte-identically:

- gantt, tree, calendar, map: deleting `$top: NON_GRID_ROW_CEILING_TOP` goes
  red at the `$top` ASSERTION and nowhere else. Not at the footnote (gantt,
  calendar, map) and not at the row count (tree): an adapter with no `$top`
  answers with the whole filtered set, `applyNonGridRowCeiling` slices it to
  the ceiling from the rows in hand, and both the drawn count and the note stay
  correct. The probe row bounds the response; it is not the detector's only
  input once an unbounded response has arrived.
- calendar and map additionally record the second ablation their new count
  assertions exist for: handing the view the raw response instead of the capped
  rows is red at the count, 2001 against 2000 — the mutation that left both
  files green at 4/4 before this branch.

A pin whose docblock names the wrong discriminator is worse than one that names
none: the next reader trusts it while deciding what an edit may safely break.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3181.9 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-geK0qt0o.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) 15.67KB 5.75KB
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) 25.05KB 9.16KB
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.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 5.13KB 2.35KB
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) 516.19KB 117.80KB
core (index.js) 6.12KB 2.42KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 178.20KB 49.60KB
fields (index.js) 242.42KB 61.26KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
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.55KB 0.62KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
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) 5.12KB 1.74KB
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) 15.75KB 3.80KB
plugin-calendar (index.js) 48.15KB 13.35KB
plugin-charts (index.js) 70.87KB 19.72KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.82KB 34.64KB
plugin-designer (index.js) 212.87KB 43.19KB
plugin-detail (index.js) 251.07KB 64.12KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.50KB 41.08KB
plugin-grid (index.js) 209.25KB 56.71KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.33KB 27.60KB
plugin-map (index.js) 20.55KB 6.80KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.51KB 11.94KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.40KB 3.23KB
plugin-view (index.js) 85.22KB 20.93KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
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 (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) 2.59KB 1.31KB
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 (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
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

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

Projects

None yet

2 participants