feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow - #7241

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx
Sep 2, 2026
Merged

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow#7241
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7188

Option B of the A/B split ruled on #7165 (comment 5491922231); #7165 remains open — its closure is the PM's, after landing. Clause-②: this widens a published, exactly-pinned context on DataTableSchema (@object-ui/types), ruled at tier in #7188 comment 5502616875 and carried unchanged.

The contract delta

DataTableSchema.renderCellEditor's context, packages/types/src/data-display.ts:

beforeafter
memberscolumn, row, value, stage, commit, cancelcolumn, row, pendingRow, value, stage, commit, cancel
rowthe persisted recordunchanged — the persisted record
pendingRowrow shallow-merged with the row's staged, unsaved edits (its pendingChanges entry); the SAME object as row when nothing is staged
  • Why row was not redefined. Making row mean the merged record would be a silent semantic change to an already-published member — the option that passes review by looking smaller — and a host that needs the persisted value would lose its only source. Both are addressable; neither is redefined. (The card's reasoning; the tier ruling adopted it.)
  • pendingRow is required, not optional, because data-table always passes it — the pin's principle is "transcribed from the call site".
  • Zod mirror (packages/types/src/zod/data-display.zod.ts:270): renderCellEditor: z.function().optional() — it encodes no parameter shape, so there is no ctx member to mirror. The only zod-side delta is the .describe() text, which now records the two-row semantics and names the member on DataTableSchema as the authority. zod-mirror-parity is about key membership and is unaffected.
  • The [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882 exact-shape pin (data-table-declared-keys-6882.test.ts) went red on the seventh member by design and was extended, not weakened: CellEditorContext gains pendingRow; the Equal (not extends) form and the stated reason are kept; a new direction proof _SixMemberShapeIsRefused (@ts-expect-error on the six-member shape) proves the instrument can tell the seventh member's presence from its absence. Leg B of the ablation shows the exact red.

Producer — packages/components/src/renderers/complex/data-table.tsx

injectEditor({ column, row, pendingRow: rowHasChanges ? pendingRows.of(row, rowIndex) : row, value, stage, commit, cancel }). The ruled expression was rowHasChanges ? { ...row, ...rowChanges } : row; the values are identical, but the merged object is cached per row object and rebuilt only when pendingChanges is replaced (every stage / save / cancel builds a new Map). Measured reason: LookupField keys its dependentFilterpopoverFilter memos on the identity of the record it is handed, and its recent-ids useEffect keys on popoverFilter, so a fresh object per render would re-issue that query on every table re-render while the picker is open (useRecordQuery itself is immune — it keys on a JSON filterSignature). row is identity-stable for the same reason. Implementation detail beneath the contract; the member and its semantics are exactly as ruled.

Consumer — packages/plugin-grid/src/ObjectGrid.tsx

The assertion B adds — gridDependentValues-7165.test.tsx, test 4

Edit the parent cell (regionsouth), do not save, open the child picker. Asserts: Person 07 (south) offered, Person 01 (north) not; the query carried $filter.region === 'south' and never 'north'; the region cell renders the pending south while rows[0].region is still north and ds.update was never called (so the staged and persisted parents cannot coincide). Tests 1–3 and the lookupPickerKeys-7154 suite are the live controls — their rows carry no staged edits, so pendingRow and row coincide there; they stay green under both A and B, which is why they cannot see the regression and test 4 can.

Ablation — after commit d20e5c4; both legs restored via git checkout HEAD --, proven by blob hash = HEAD blob and empty git diff HEAD

  • Leg AObjectGrid.tsx back to dependentValues={ctx.row} (mutation proven on disk: B-line count 1→0, A-line 0→1, blob hash ≠ HEAD): Tests 1 failed | 8 passed (9) — exactly test 4 (× 4 — ⭐ objectui#7188: a STAGED parent re-scopes the child…) red; tests 1–3 and all 7154 tests green. No dist rebuild is involved: the root vitest.config.mts aliases @object-ui/types / components / fields / react / plugin-grid to src (lines 281–288), so the suite reads the mutated source directly.
  • Leg B — remove pendingRow: any; from the declared ctx (member count 1→0, blob hash ≠ HEAD): tsc -p tsconfig.test.json in packages/types exits 2 with (127,3) TS2344 on _RenderCellEditorShape, (143,1) TS2578: Unused '@ts-expect-error' directive on _SixMemberShapeIsRefused, and (164,40) TS2339: Property 'pendingRow' does not exist on the authoring example. Direction: red, as predicted; the control directive flips exactly as designed.

Census of renderCellEditor ctx readers

Grep over the whole tree excluding node_modules / dist, control term ctx.row hit (in app-shell/resolveActionParams.ts — a different ctx — and in plugin-grid). examples/, apps/, plugin-dashboard, plugin-detail, app-shell: no reader.

readermembers readeffect of the additive member
packages/plugin-grid/src/ObjectGrid.tsx — the only production consumercolumn, row, value, stage, commit, now pendingRowupdated here
packages/components/src/renderers/complex/data-table.tsxproducer of the ctxupdated here
packages/components/src/__tests__/data-table-injected-editor-focus-6859.test.tsxstagingEditor({ column, value, stage })threeunaffected, green
packages/components/src/__tests__/data-table-inline-edit.test.tsx:192({ column, stage })twounaffected, green
packages/plugin-grid/src/__tests__/dataTableSchemaSlot-6459.test.ts:158(ctx) => ctx.columnone, contextually typed from the seam intersectionunaffected, compiles (the hold copy kept Equal)
packages/types/src/__tests__/data-table-declared-keys-6882.test.tsthe pinextended here
content/docs/components/complex/data-table.mdx:50the documented ctxupdated here

One file outside the dispatched surface — declared

content/docs/components/complex/data-table.mdx (+9/−1): the published documentation of exactly this ctx listed six members. AGENTS.md Commandment #2 (docs reflect the code) is binding, and leaving it would ship documentation that contradicts the type it documents. Drop the hunk if the PM prefers it filed separately.

Verification on d20e5c4 (the pushed head)

  • build: pnpm --filter @object-ui/components --filter @object-ui/plugin-grid build (prebuild rebuilt types / core / react) — green; type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-grid (each tsc --noEmit && tsc -p tsconfig.test.json; types also tsconfig.examples.json) — zero diagnostics; --listFiles confirms the 6882 pin (1) and the 6459 + 7165 files (2) are in their tsconfig.test.json programs. Lock verdict line: os-verify-lock: VERDICT command-exit 0.
  • vitest, root-relative, 7 files (the two grid fixtures, the 6882 pin, the 6459 slot pin, data-table-inline-edit, data-table-injected-editor-focus-6859, inlineEditLookupRepro): Test Files 7 passed (7) / Tests 31 passed (31).
  • gates green: check-changeset-presence (3 changesets for 3 released packages), check-changeset-no-major, check:control-bytes, check:spec-symbols, check:doc-types, check:doc-fences, check-doc-links, check:dist-completeness; eslint on the seven changed ts/tsx files — 0 errors.
  • NOT measured locally (precondition exit 2 — they need the full-farm / console build; CI owns them): check:sdui-registration-pins, check:eager-closure, check:doc-snippets, check:readme-exports. This diff touches no registration and no README, and the data-table.mdx snippet is a plaintext fence.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

…enderCellEditor seam as `pendingRow`
`renderCellEditor`'s context on `DataTableSchema` gains a seventh member,
`pendingRow` — the persisted `row` shallow-merged with the row's staged,
unsaved edits. `row` keeps meaning the persisted record; neither is
redefined. `data-table` builds it from `pendingChanges`, identity-stable per
row while nothing new is staged, so a dependent widget's identity-keyed fetch
effects do not re-fire on every table re-render. `ObjectGrid` scopes a
`dependsOn` inline editor by `ctx.pendingRow ?? ctx.row`, so a parent edited
in the same row re-scopes the child before anything is saved, and the interim
marker that named the follow-up is gone with the interim.
The exact-shape pin in `@object-ui/types` is extended, not weakened, and
gains a control proving it can tell the seventh member's presence from its
absence. Test 4 of the grid fixture flips from pinning the saved-row
staleness to asserting that the staged parent scopes the child, with its own
proof that the staging landed and nothing was saved. The zod mirror encodes
`renderCellEditor` as `z.function()` with no parameter shape; only its
description records the delta. The docs snippet and prose for `data-table`
carry the new member.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.2 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-BDpvv2yk.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.75KB
plugin-grid (index.js)208.88KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.55KB27.68KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-litant
os-litant marked this pull request as ready for review September 2, 2026 01:48
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit adbda1bSep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-7188-pending-row-ctx branch September 2, 2026 02:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow - #7241

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx
Sep 2, 2026
Merged

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow#7241
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7188

Option B of the A/B split ruled on #7165 (comment 5491922231); #7165 remains open — its closure is the PM's, after landing. Clause-②: this widens a published, exactly-pinned context on DataTableSchema (@object-ui/types), ruled at tier in #7188 comment 5502616875 and carried unchanged.

The contract delta

DataTableSchema.renderCellEditor's context, packages/types/src/data-display.ts:

beforeafter
memberscolumn, row, value, stage, commit, cancelcolumn, row, pendingRow, value, stage, commit, cancel
rowthe persisted recordunchanged — the persisted record
pendingRowrow shallow-merged with the row's staged, unsaved edits (its pendingChanges entry); the SAME object as row when nothing is staged
  • Why row was not redefined. Making row mean the merged record would be a silent semantic change to an already-published member — the option that passes review by looking smaller — and a host that needs the persisted value would lose its only source. Both are addressable; neither is redefined. (The card's reasoning; the tier ruling adopted it.)
  • pendingRow is required, not optional, because data-table always passes it — the pin's principle is "transcribed from the call site".
  • Zod mirror (packages/types/src/zod/data-display.zod.ts:270): renderCellEditor: z.function().optional() — it encodes no parameter shape, so there is no ctx member to mirror. The only zod-side delta is the .describe() text, which now records the two-row semantics and names the member on DataTableSchema as the authority. zod-mirror-parity is about key membership and is unaffected.
  • The [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882 exact-shape pin (data-table-declared-keys-6882.test.ts) went red on the seventh member by design and was extended, not weakened: CellEditorContext gains pendingRow; the Equal (not extends) form and the stated reason are kept; a new direction proof _SixMemberShapeIsRefused (@ts-expect-error on the six-member shape) proves the instrument can tell the seventh member's presence from its absence. Leg B of the ablation shows the exact red.

Producer — packages/components/src/renderers/complex/data-table.tsx

injectEditor({ column, row, pendingRow: rowHasChanges ? pendingRows.of(row, rowIndex) : row, value, stage, commit, cancel }). The ruled expression was rowHasChanges ? { ...row, ...rowChanges } : row; the values are identical, but the merged object is cached per row object and rebuilt only when pendingChanges is replaced (every stage / save / cancel builds a new Map). Measured reason: LookupField keys its dependentFilterpopoverFilter memos on the identity of the record it is handed, and its recent-ids useEffect keys on popoverFilter, so a fresh object per render would re-issue that query on every table re-render while the picker is open (useRecordQuery itself is immune — it keys on a JSON filterSignature). row is identity-stable for the same reason. Implementation detail beneath the contract; the member and its semantics are exactly as ruled.

Consumer — packages/plugin-grid/src/ObjectGrid.tsx

The assertion B adds — gridDependentValues-7165.test.tsx, test 4

Edit the parent cell (regionsouth), do not save, open the child picker. Asserts: Person 07 (south) offered, Person 01 (north) not; the query carried $filter.region === 'south' and never 'north'; the region cell renders the pending south while rows[0].region is still north and ds.update was never called (so the staged and persisted parents cannot coincide). Tests 1–3 and the lookupPickerKeys-7154 suite are the live controls — their rows carry no staged edits, so pendingRow and row coincide there; they stay green under both A and B, which is why they cannot see the regression and test 4 can.

Ablation — after commit d20e5c4; both legs restored via git checkout HEAD --, proven by blob hash = HEAD blob and empty git diff HEAD

  • Leg AObjectGrid.tsx back to dependentValues={ctx.row} (mutation proven on disk: B-line count 1→0, A-line 0→1, blob hash ≠ HEAD): Tests 1 failed | 8 passed (9) — exactly test 4 (× 4 — ⭐ objectui#7188: a STAGED parent re-scopes the child…) red; tests 1–3 and all 7154 tests green. No dist rebuild is involved: the root vitest.config.mts aliases @object-ui/types / components / fields / react / plugin-grid to src (lines 281–288), so the suite reads the mutated source directly.
  • Leg B — remove pendingRow: any; from the declared ctx (member count 1→0, blob hash ≠ HEAD): tsc -p tsconfig.test.json in packages/types exits 2 with (127,3) TS2344 on _RenderCellEditorShape, (143,1) TS2578: Unused '@ts-expect-error' directive on _SixMemberShapeIsRefused, and (164,40) TS2339: Property 'pendingRow' does not exist on the authoring example. Direction: red, as predicted; the control directive flips exactly as designed.

Census of renderCellEditor ctx readers

Grep over the whole tree excluding node_modules / dist, control term ctx.row hit (in app-shell/resolveActionParams.ts — a different ctx — and in plugin-grid). examples/, apps/, plugin-dashboard, plugin-detail, app-shell: no reader.

readermembers readeffect of the additive member
packages/plugin-grid/src/ObjectGrid.tsx — the only production consumercolumn, row, value, stage, commit, now pendingRowupdated here
packages/components/src/renderers/complex/data-table.tsxproducer of the ctxupdated here
packages/components/src/__tests__/data-table-injected-editor-focus-6859.test.tsxstagingEditor({ column, value, stage })threeunaffected, green
packages/components/src/__tests__/data-table-inline-edit.test.tsx:192({ column, stage })twounaffected, green
packages/plugin-grid/src/__tests__/dataTableSchemaSlot-6459.test.ts:158(ctx) => ctx.columnone, contextually typed from the seam intersectionunaffected, compiles (the hold copy kept Equal)
packages/types/src/__tests__/data-table-declared-keys-6882.test.tsthe pinextended here
content/docs/components/complex/data-table.mdx:50the documented ctxupdated here

One file outside the dispatched surface — declared

content/docs/components/complex/data-table.mdx (+9/−1): the published documentation of exactly this ctx listed six members. AGENTS.md Commandment #2 (docs reflect the code) is binding, and leaving it would ship documentation that contradicts the type it documents. Drop the hunk if the PM prefers it filed separately.

Verification on d20e5c4 (the pushed head)

  • build: pnpm --filter @object-ui/components --filter @object-ui/plugin-grid build (prebuild rebuilt types / core / react) — green; type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-grid (each tsc --noEmit && tsc -p tsconfig.test.json; types also tsconfig.examples.json) — zero diagnostics; --listFiles confirms the 6882 pin (1) and the 6459 + 7165 files (2) are in their tsconfig.test.json programs. Lock verdict line: os-verify-lock: VERDICT command-exit 0.
  • vitest, root-relative, 7 files (the two grid fixtures, the 6882 pin, the 6459 slot pin, data-table-inline-edit, data-table-injected-editor-focus-6859, inlineEditLookupRepro): Test Files 7 passed (7) / Tests 31 passed (31).
  • gates green: check-changeset-presence (3 changesets for 3 released packages), check-changeset-no-major, check:control-bytes, check:spec-symbols, check:doc-types, check:doc-fences, check-doc-links, check:dist-completeness; eslint on the seven changed ts/tsx files — 0 errors.
  • NOT measured locally (precondition exit 2 — they need the full-farm / console build; CI owns them): check:sdui-registration-pins, check:eager-closure, check:doc-snippets, check:readme-exports. This diff touches no registration and no README, and the data-table.mdx snippet is a plaintext fence.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

…enderCellEditor seam as `pendingRow`
`renderCellEditor`'s context on `DataTableSchema` gains a seventh member,
`pendingRow` — the persisted `row` shallow-merged with the row's staged,
unsaved edits. `row` keeps meaning the persisted record; neither is
redefined. `data-table` builds it from `pendingChanges`, identity-stable per
row while nothing new is staged, so a dependent widget's identity-keyed fetch
effects do not re-fire on every table re-render. `ObjectGrid` scopes a
`dependsOn` inline editor by `ctx.pendingRow ?? ctx.row`, so a parent edited
in the same row re-scopes the child before anything is saved, and the interim
marker that named the follow-up is gone with the interim.
The exact-shape pin in `@object-ui/types` is extended, not weakened, and
gains a control proving it can tell the seventh member's presence from its
absence. Test 4 of the grid fixture flips from pinning the saved-row
staleness to asserting that the staged parent scopes the child, with its own
proof that the staging landed and nothing was saved. The zod mirror encodes
`renderCellEditor` as `z.function()` with no parameter shape; only its
description records the delta. The docs snippet and prose for `data-table`
carry the new member.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.2 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-BDpvv2yk.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.75KB
plugin-grid (index.js)208.88KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.55KB27.68KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-litant
os-litant marked this pull request as ready for review September 2, 2026 01:48
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit adbda1bSep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-7188-pending-row-ctx branch September 2, 2026 02:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow - #7241

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx
Sep 2, 2026
Merged

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow#7241
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7188

Option B of the A/B split ruled on #7165 (comment 5491922231); #7165 remains open — its closure is the PM's, after landing. Clause-②: this widens a published, exactly-pinned context on DataTableSchema (@object-ui/types), ruled at tier in #7188 comment 5502616875 and carried unchanged.

The contract delta

DataTableSchema.renderCellEditor's context, packages/types/src/data-display.ts:

beforeafter
memberscolumn, row, value, stage, commit, cancelcolumn, row, pendingRow, value, stage, commit, cancel
rowthe persisted recordunchanged — the persisted record
pendingRowrow shallow-merged with the row's staged, unsaved edits (its pendingChanges entry); the SAME object as row when nothing is staged
  • Why row was not redefined. Making row mean the merged record would be a silent semantic change to an already-published member — the option that passes review by looking smaller — and a host that needs the persisted value would lose its only source. Both are addressable; neither is redefined. (The card's reasoning; the tier ruling adopted it.)
  • pendingRow is required, not optional, because data-table always passes it — the pin's principle is "transcribed from the call site".
  • Zod mirror (packages/types/src/zod/data-display.zod.ts:270): renderCellEditor: z.function().optional() — it encodes no parameter shape, so there is no ctx member to mirror. The only zod-side delta is the .describe() text, which now records the two-row semantics and names the member on DataTableSchema as the authority. zod-mirror-parity is about key membership and is unaffected.
  • The [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882 exact-shape pin (data-table-declared-keys-6882.test.ts) went red on the seventh member by design and was extended, not weakened: CellEditorContext gains pendingRow; the Equal (not extends) form and the stated reason are kept; a new direction proof _SixMemberShapeIsRefused (@ts-expect-error on the six-member shape) proves the instrument can tell the seventh member's presence from its absence. Leg B of the ablation shows the exact red.

Producer — packages/components/src/renderers/complex/data-table.tsx

injectEditor({ column, row, pendingRow: rowHasChanges ? pendingRows.of(row, rowIndex) : row, value, stage, commit, cancel }). The ruled expression was rowHasChanges ? { ...row, ...rowChanges } : row; the values are identical, but the merged object is cached per row object and rebuilt only when pendingChanges is replaced (every stage / save / cancel builds a new Map). Measured reason: LookupField keys its dependentFilterpopoverFilter memos on the identity of the record it is handed, and its recent-ids useEffect keys on popoverFilter, so a fresh object per render would re-issue that query on every table re-render while the picker is open (useRecordQuery itself is immune — it keys on a JSON filterSignature). row is identity-stable for the same reason. Implementation detail beneath the contract; the member and its semantics are exactly as ruled.

Consumer — packages/plugin-grid/src/ObjectGrid.tsx

The assertion B adds — gridDependentValues-7165.test.tsx, test 4

Edit the parent cell (regionsouth), do not save, open the child picker. Asserts: Person 07 (south) offered, Person 01 (north) not; the query carried $filter.region === 'south' and never 'north'; the region cell renders the pending south while rows[0].region is still north and ds.update was never called (so the staged and persisted parents cannot coincide). Tests 1–3 and the lookupPickerKeys-7154 suite are the live controls — their rows carry no staged edits, so pendingRow and row coincide there; they stay green under both A and B, which is why they cannot see the regression and test 4 can.

Ablation — after commit d20e5c4; both legs restored via git checkout HEAD --, proven by blob hash = HEAD blob and empty git diff HEAD

  • Leg AObjectGrid.tsx back to dependentValues={ctx.row} (mutation proven on disk: B-line count 1→0, A-line 0→1, blob hash ≠ HEAD): Tests 1 failed | 8 passed (9) — exactly test 4 (× 4 — ⭐ objectui#7188: a STAGED parent re-scopes the child…) red; tests 1–3 and all 7154 tests green. No dist rebuild is involved: the root vitest.config.mts aliases @object-ui/types / components / fields / react / plugin-grid to src (lines 281–288), so the suite reads the mutated source directly.
  • Leg B — remove pendingRow: any; from the declared ctx (member count 1→0, blob hash ≠ HEAD): tsc -p tsconfig.test.json in packages/types exits 2 with (127,3) TS2344 on _RenderCellEditorShape, (143,1) TS2578: Unused '@ts-expect-error' directive on _SixMemberShapeIsRefused, and (164,40) TS2339: Property 'pendingRow' does not exist on the authoring example. Direction: red, as predicted; the control directive flips exactly as designed.

Census of renderCellEditor ctx readers

Grep over the whole tree excluding node_modules / dist, control term ctx.row hit (in app-shell/resolveActionParams.ts — a different ctx — and in plugin-grid). examples/, apps/, plugin-dashboard, plugin-detail, app-shell: no reader.

readermembers readeffect of the additive member
packages/plugin-grid/src/ObjectGrid.tsx — the only production consumercolumn, row, value, stage, commit, now pendingRowupdated here
packages/components/src/renderers/complex/data-table.tsxproducer of the ctxupdated here
packages/components/src/__tests__/data-table-injected-editor-focus-6859.test.tsxstagingEditor({ column, value, stage })threeunaffected, green
packages/components/src/__tests__/data-table-inline-edit.test.tsx:192({ column, stage })twounaffected, green
packages/plugin-grid/src/__tests__/dataTableSchemaSlot-6459.test.ts:158(ctx) => ctx.columnone, contextually typed from the seam intersectionunaffected, compiles (the hold copy kept Equal)
packages/types/src/__tests__/data-table-declared-keys-6882.test.tsthe pinextended here
content/docs/components/complex/data-table.mdx:50the documented ctxupdated here

One file outside the dispatched surface — declared

content/docs/components/complex/data-table.mdx (+9/−1): the published documentation of exactly this ctx listed six members. AGENTS.md Commandment #2 (docs reflect the code) is binding, and leaving it would ship documentation that contradicts the type it documents. Drop the hunk if the PM prefers it filed separately.

Verification on d20e5c4 (the pushed head)

  • build: pnpm --filter @object-ui/components --filter @object-ui/plugin-grid build (prebuild rebuilt types / core / react) — green; type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-grid (each tsc --noEmit && tsc -p tsconfig.test.json; types also tsconfig.examples.json) — zero diagnostics; --listFiles confirms the 6882 pin (1) and the 6459 + 7165 files (2) are in their tsconfig.test.json programs. Lock verdict line: os-verify-lock: VERDICT command-exit 0.
  • vitest, root-relative, 7 files (the two grid fixtures, the 6882 pin, the 6459 slot pin, data-table-inline-edit, data-table-injected-editor-focus-6859, inlineEditLookupRepro): Test Files 7 passed (7) / Tests 31 passed (31).
  • gates green: check-changeset-presence (3 changesets for 3 released packages), check-changeset-no-major, check:control-bytes, check:spec-symbols, check:doc-types, check:doc-fences, check-doc-links, check:dist-completeness; eslint on the seven changed ts/tsx files — 0 errors.
  • NOT measured locally (precondition exit 2 — they need the full-farm / console build; CI owns them): check:sdui-registration-pins, check:eager-closure, check:doc-snippets, check:readme-exports. This diff touches no registration and no README, and the data-table.mdx snippet is a plaintext fence.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

…enderCellEditor seam as `pendingRow`
`renderCellEditor`'s context on `DataTableSchema` gains a seventh member,
`pendingRow` — the persisted `row` shallow-merged with the row's staged,
unsaved edits. `row` keeps meaning the persisted record; neither is
redefined. `data-table` builds it from `pendingChanges`, identity-stable per
row while nothing new is staged, so a dependent widget's identity-keyed fetch
effects do not re-fire on every table re-render. `ObjectGrid` scopes a
`dependsOn` inline editor by `ctx.pendingRow ?? ctx.row`, so a parent edited
in the same row re-scopes the child before anything is saved, and the interim
marker that named the follow-up is gone with the interim.
The exact-shape pin in `@object-ui/types` is extended, not weakened, and
gains a control proving it can tell the seventh member's presence from its
absence. Test 4 of the grid fixture flips from pinning the saved-row
staleness to asserting that the staged parent scopes the child, with its own
proof that the staging landed and nothing was saved. The zod mirror encodes
`renderCellEditor` as `z.function()` with no parameter shape; only its
description records the delta. The docs snippet and prose for `data-table`
carry the new member.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.2 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-BDpvv2yk.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.75KB
plugin-grid (index.js)208.88KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.55KB27.68KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-litant
os-litant marked this pull request as ready for review September 2, 2026 01:48
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit adbda1bSep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-7188-pending-row-ctx branch September 2, 2026 02:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow - #7241

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx
Sep 2, 2026
Merged

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow#7241
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7188

Option B of the A/B split ruled on #7165 (comment 5491922231); #7165 remains open — its closure is the PM's, after landing. Clause-②: this widens a published, exactly-pinned context on DataTableSchema (@object-ui/types), ruled at tier in #7188 comment 5502616875 and carried unchanged.

The contract delta

DataTableSchema.renderCellEditor's context, packages/types/src/data-display.ts:

beforeafter
memberscolumn, row, value, stage, commit, cancelcolumn, row, pendingRow, value, stage, commit, cancel
rowthe persisted recordunchanged — the persisted record
pendingRowrow shallow-merged with the row's staged, unsaved edits (its pendingChanges entry); the SAME object as row when nothing is staged
  • Why row was not redefined. Making row mean the merged record would be a silent semantic change to an already-published member — the option that passes review by looking smaller — and a host that needs the persisted value would lose its only source. Both are addressable; neither is redefined. (The card's reasoning; the tier ruling adopted it.)
  • pendingRow is required, not optional, because data-table always passes it — the pin's principle is "transcribed from the call site".
  • Zod mirror (packages/types/src/zod/data-display.zod.ts:270): renderCellEditor: z.function().optional() — it encodes no parameter shape, so there is no ctx member to mirror. The only zod-side delta is the .describe() text, which now records the two-row semantics and names the member on DataTableSchema as the authority. zod-mirror-parity is about key membership and is unaffected.
  • The [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882 exact-shape pin (data-table-declared-keys-6882.test.ts) went red on the seventh member by design and was extended, not weakened: CellEditorContext gains pendingRow; the Equal (not extends) form and the stated reason are kept; a new direction proof _SixMemberShapeIsRefused (@ts-expect-error on the six-member shape) proves the instrument can tell the seventh member's presence from its absence. Leg B of the ablation shows the exact red.

Producer — packages/components/src/renderers/complex/data-table.tsx

injectEditor({ column, row, pendingRow: rowHasChanges ? pendingRows.of(row, rowIndex) : row, value, stage, commit, cancel }). The ruled expression was rowHasChanges ? { ...row, ...rowChanges } : row; the values are identical, but the merged object is cached per row object and rebuilt only when pendingChanges is replaced (every stage / save / cancel builds a new Map). Measured reason: LookupField keys its dependentFilterpopoverFilter memos on the identity of the record it is handed, and its recent-ids useEffect keys on popoverFilter, so a fresh object per render would re-issue that query on every table re-render while the picker is open (useRecordQuery itself is immune — it keys on a JSON filterSignature). row is identity-stable for the same reason. Implementation detail beneath the contract; the member and its semantics are exactly as ruled.

Consumer — packages/plugin-grid/src/ObjectGrid.tsx

The assertion B adds — gridDependentValues-7165.test.tsx, test 4

Edit the parent cell (regionsouth), do not save, open the child picker. Asserts: Person 07 (south) offered, Person 01 (north) not; the query carried $filter.region === 'south' and never 'north'; the region cell renders the pending south while rows[0].region is still north and ds.update was never called (so the staged and persisted parents cannot coincide). Tests 1–3 and the lookupPickerKeys-7154 suite are the live controls — their rows carry no staged edits, so pendingRow and row coincide there; they stay green under both A and B, which is why they cannot see the regression and test 4 can.

Ablation — after commit d20e5c4; both legs restored via git checkout HEAD --, proven by blob hash = HEAD blob and empty git diff HEAD

  • Leg AObjectGrid.tsx back to dependentValues={ctx.row} (mutation proven on disk: B-line count 1→0, A-line 0→1, blob hash ≠ HEAD): Tests 1 failed | 8 passed (9) — exactly test 4 (× 4 — ⭐ objectui#7188: a STAGED parent re-scopes the child…) red; tests 1–3 and all 7154 tests green. No dist rebuild is involved: the root vitest.config.mts aliases @object-ui/types / components / fields / react / plugin-grid to src (lines 281–288), so the suite reads the mutated source directly.
  • Leg B — remove pendingRow: any; from the declared ctx (member count 1→0, blob hash ≠ HEAD): tsc -p tsconfig.test.json in packages/types exits 2 with (127,3) TS2344 on _RenderCellEditorShape, (143,1) TS2578: Unused '@ts-expect-error' directive on _SixMemberShapeIsRefused, and (164,40) TS2339: Property 'pendingRow' does not exist on the authoring example. Direction: red, as predicted; the control directive flips exactly as designed.

Census of renderCellEditor ctx readers

Grep over the whole tree excluding node_modules / dist, control term ctx.row hit (in app-shell/resolveActionParams.ts — a different ctx — and in plugin-grid). examples/, apps/, plugin-dashboard, plugin-detail, app-shell: no reader.

readermembers readeffect of the additive member
packages/plugin-grid/src/ObjectGrid.tsx — the only production consumercolumn, row, value, stage, commit, now pendingRowupdated here
packages/components/src/renderers/complex/data-table.tsxproducer of the ctxupdated here
packages/components/src/__tests__/data-table-injected-editor-focus-6859.test.tsxstagingEditor({ column, value, stage })threeunaffected, green
packages/components/src/__tests__/data-table-inline-edit.test.tsx:192({ column, stage })twounaffected, green
packages/plugin-grid/src/__tests__/dataTableSchemaSlot-6459.test.ts:158(ctx) => ctx.columnone, contextually typed from the seam intersectionunaffected, compiles (the hold copy kept Equal)
packages/types/src/__tests__/data-table-declared-keys-6882.test.tsthe pinextended here
content/docs/components/complex/data-table.mdx:50the documented ctxupdated here

One file outside the dispatched surface — declared

content/docs/components/complex/data-table.mdx (+9/−1): the published documentation of exactly this ctx listed six members. AGENTS.md Commandment #2 (docs reflect the code) is binding, and leaving it would ship documentation that contradicts the type it documents. Drop the hunk if the PM prefers it filed separately.

Verification on d20e5c4 (the pushed head)

  • build: pnpm --filter @object-ui/components --filter @object-ui/plugin-grid build (prebuild rebuilt types / core / react) — green; type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-grid (each tsc --noEmit && tsc -p tsconfig.test.json; types also tsconfig.examples.json) — zero diagnostics; --listFiles confirms the 6882 pin (1) and the 6459 + 7165 files (2) are in their tsconfig.test.json programs. Lock verdict line: os-verify-lock: VERDICT command-exit 0.
  • vitest, root-relative, 7 files (the two grid fixtures, the 6882 pin, the 6459 slot pin, data-table-inline-edit, data-table-injected-editor-focus-6859, inlineEditLookupRepro): Test Files 7 passed (7) / Tests 31 passed (31).
  • gates green: check-changeset-presence (3 changesets for 3 released packages), check-changeset-no-major, check:control-bytes, check:spec-symbols, check:doc-types, check:doc-fences, check-doc-links, check:dist-completeness; eslint on the seven changed ts/tsx files — 0 errors.
  • NOT measured locally (precondition exit 2 — they need the full-farm / console build; CI owns them): check:sdui-registration-pins, check:eager-closure, check:doc-snippets, check:readme-exports. This diff touches no registration and no README, and the data-table.mdx snippet is a plaintext fence.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

…enderCellEditor seam as `pendingRow`
`renderCellEditor`'s context on `DataTableSchema` gains a seventh member,
`pendingRow` — the persisted `row` shallow-merged with the row's staged,
unsaved edits. `row` keeps meaning the persisted record; neither is
redefined. `data-table` builds it from `pendingChanges`, identity-stable per
row while nothing new is staged, so a dependent widget's identity-keyed fetch
effects do not re-fire on every table re-render. `ObjectGrid` scopes a
`dependsOn` inline editor by `ctx.pendingRow ?? ctx.row`, so a parent edited
in the same row re-scopes the child before anything is saved, and the interim
marker that named the follow-up is gone with the interim.
The exact-shape pin in `@object-ui/types` is extended, not weakened, and
gains a control proving it can tell the seventh member's presence from its
absence. Test 4 of the grid fixture flips from pinning the saved-row
staleness to asserting that the staged parent scopes the child, with its own
proof that the staging landed and nothing was saved. The zod mirror encodes
`renderCellEditor` as `z.function()` with no parameter shape; only its
description records the delta. The docs snippet and prose for `data-table`
carry the new member.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.2 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-BDpvv2yk.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.75KB
plugin-grid (index.js)208.88KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.55KB27.68KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-litant
os-litant marked this pull request as ready for review September 2, 2026 01:48
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit adbda1bSep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-7188-pending-row-ctx branch September 2, 2026 02:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow - #7241

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx
Sep 2, 2026
Merged

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow#7241
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7188

Option B of the A/B split ruled on #7165 (comment 5491922231); #7165 remains open — its closure is the PM's, after landing. Clause-②: this widens a published, exactly-pinned context on DataTableSchema (@object-ui/types), ruled at tier in #7188 comment 5502616875 and carried unchanged.

The contract delta

DataTableSchema.renderCellEditor's context, packages/types/src/data-display.ts:

beforeafter
memberscolumn, row, value, stage, commit, cancelcolumn, row, pendingRow, value, stage, commit, cancel
rowthe persisted recordunchanged — the persisted record
pendingRowrow shallow-merged with the row's staged, unsaved edits (its pendingChanges entry); the SAME object as row when nothing is staged
  • Why row was not redefined. Making row mean the merged record would be a silent semantic change to an already-published member — the option that passes review by looking smaller — and a host that needs the persisted value would lose its only source. Both are addressable; neither is redefined. (The card's reasoning; the tier ruling adopted it.)
  • pendingRow is required, not optional, because data-table always passes it — the pin's principle is "transcribed from the call site".
  • Zod mirror (packages/types/src/zod/data-display.zod.ts:270): renderCellEditor: z.function().optional() — it encodes no parameter shape, so there is no ctx member to mirror. The only zod-side delta is the .describe() text, which now records the two-row semantics and names the member on DataTableSchema as the authority. zod-mirror-parity is about key membership and is unaffected.
  • The [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882 exact-shape pin (data-table-declared-keys-6882.test.ts) went red on the seventh member by design and was extended, not weakened: CellEditorContext gains pendingRow; the Equal (not extends) form and the stated reason are kept; a new direction proof _SixMemberShapeIsRefused (@ts-expect-error on the six-member shape) proves the instrument can tell the seventh member's presence from its absence. Leg B of the ablation shows the exact red.

Producer — packages/components/src/renderers/complex/data-table.tsx

injectEditor({ column, row, pendingRow: rowHasChanges ? pendingRows.of(row, rowIndex) : row, value, stage, commit, cancel }). The ruled expression was rowHasChanges ? { ...row, ...rowChanges } : row; the values are identical, but the merged object is cached per row object and rebuilt only when pendingChanges is replaced (every stage / save / cancel builds a new Map). Measured reason: LookupField keys its dependentFilterpopoverFilter memos on the identity of the record it is handed, and its recent-ids useEffect keys on popoverFilter, so a fresh object per render would re-issue that query on every table re-render while the picker is open (useRecordQuery itself is immune — it keys on a JSON filterSignature). row is identity-stable for the same reason. Implementation detail beneath the contract; the member and its semantics are exactly as ruled.

Consumer — packages/plugin-grid/src/ObjectGrid.tsx

The assertion B adds — gridDependentValues-7165.test.tsx, test 4

Edit the parent cell (regionsouth), do not save, open the child picker. Asserts: Person 07 (south) offered, Person 01 (north) not; the query carried $filter.region === 'south' and never 'north'; the region cell renders the pending south while rows[0].region is still north and ds.update was never called (so the staged and persisted parents cannot coincide). Tests 1–3 and the lookupPickerKeys-7154 suite are the live controls — their rows carry no staged edits, so pendingRow and row coincide there; they stay green under both A and B, which is why they cannot see the regression and test 4 can.

Ablation — after commit d20e5c4; both legs restored via git checkout HEAD --, proven by blob hash = HEAD blob and empty git diff HEAD

  • Leg AObjectGrid.tsx back to dependentValues={ctx.row} (mutation proven on disk: B-line count 1→0, A-line 0→1, blob hash ≠ HEAD): Tests 1 failed | 8 passed (9) — exactly test 4 (× 4 — ⭐ objectui#7188: a STAGED parent re-scopes the child…) red; tests 1–3 and all 7154 tests green. No dist rebuild is involved: the root vitest.config.mts aliases @object-ui/types / components / fields / react / plugin-grid to src (lines 281–288), so the suite reads the mutated source directly.
  • Leg B — remove pendingRow: any; from the declared ctx (member count 1→0, blob hash ≠ HEAD): tsc -p tsconfig.test.json in packages/types exits 2 with (127,3) TS2344 on _RenderCellEditorShape, (143,1) TS2578: Unused '@ts-expect-error' directive on _SixMemberShapeIsRefused, and (164,40) TS2339: Property 'pendingRow' does not exist on the authoring example. Direction: red, as predicted; the control directive flips exactly as designed.

Census of renderCellEditor ctx readers

Grep over the whole tree excluding node_modules / dist, control term ctx.row hit (in app-shell/resolveActionParams.ts — a different ctx — and in plugin-grid). examples/, apps/, plugin-dashboard, plugin-detail, app-shell: no reader.

readermembers readeffect of the additive member
packages/plugin-grid/src/ObjectGrid.tsx — the only production consumercolumn, row, value, stage, commit, now pendingRowupdated here
packages/components/src/renderers/complex/data-table.tsxproducer of the ctxupdated here
packages/components/src/__tests__/data-table-injected-editor-focus-6859.test.tsxstagingEditor({ column, value, stage })threeunaffected, green
packages/components/src/__tests__/data-table-inline-edit.test.tsx:192({ column, stage })twounaffected, green
packages/plugin-grid/src/__tests__/dataTableSchemaSlot-6459.test.ts:158(ctx) => ctx.columnone, contextually typed from the seam intersectionunaffected, compiles (the hold copy kept Equal)
packages/types/src/__tests__/data-table-declared-keys-6882.test.tsthe pinextended here
content/docs/components/complex/data-table.mdx:50the documented ctxupdated here

One file outside the dispatched surface — declared

content/docs/components/complex/data-table.mdx (+9/−1): the published documentation of exactly this ctx listed six members. AGENTS.md Commandment #2 (docs reflect the code) is binding, and leaving it would ship documentation that contradicts the type it documents. Drop the hunk if the PM prefers it filed separately.

Verification on d20e5c4 (the pushed head)

  • build: pnpm --filter @object-ui/components --filter @object-ui/plugin-grid build (prebuild rebuilt types / core / react) — green; type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-grid (each tsc --noEmit && tsc -p tsconfig.test.json; types also tsconfig.examples.json) — zero diagnostics; --listFiles confirms the 6882 pin (1) and the 6459 + 7165 files (2) are in their tsconfig.test.json programs. Lock verdict line: os-verify-lock: VERDICT command-exit 0.
  • vitest, root-relative, 7 files (the two grid fixtures, the 6882 pin, the 6459 slot pin, data-table-inline-edit, data-table-injected-editor-focus-6859, inlineEditLookupRepro): Test Files 7 passed (7) / Tests 31 passed (31).
  • gates green: check-changeset-presence (3 changesets for 3 released packages), check-changeset-no-major, check:control-bytes, check:spec-symbols, check:doc-types, check:doc-fences, check-doc-links, check:dist-completeness; eslint on the seven changed ts/tsx files — 0 errors.
  • NOT measured locally (precondition exit 2 — they need the full-farm / console build; CI owns them): check:sdui-registration-pins, check:eager-closure, check:doc-snippets, check:readme-exports. This diff touches no registration and no README, and the data-table.mdx snippet is a plaintext fence.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

…enderCellEditor seam as `pendingRow`
`renderCellEditor`'s context on `DataTableSchema` gains a seventh member,
`pendingRow` — the persisted `row` shallow-merged with the row's staged,
unsaved edits. `row` keeps meaning the persisted record; neither is
redefined. `data-table` builds it from `pendingChanges`, identity-stable per
row while nothing new is staged, so a dependent widget's identity-keyed fetch
effects do not re-fire on every table re-render. `ObjectGrid` scopes a
`dependsOn` inline editor by `ctx.pendingRow ?? ctx.row`, so a parent edited
in the same row re-scopes the child before anything is saved, and the interim
marker that named the follow-up is gone with the interim.
The exact-shape pin in `@object-ui/types` is extended, not weakened, and
gains a control proving it can tell the seventh member's presence from its
absence. Test 4 of the grid fixture flips from pinning the saved-row
staleness to asserting that the staged parent scopes the child, with its own
proof that the staging landed and nothing was saved. The zod mirror encodes
`renderCellEditor` as `z.function()` with no parameter shape; only its
description records the delta. The docs snippet and prose for `data-table`
carry the new member.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.2 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-BDpvv2yk.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.75KB
plugin-grid (index.js)208.88KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.55KB27.68KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-litant
os-litant marked this pull request as ready for review September 2, 2026 01:48
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit adbda1bSep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-7188-pending-row-ctx branch September 2, 2026 02:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow - #7241

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx
Sep 2, 2026
Merged

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow#7241
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7188

Option B of the A/B split ruled on #7165 (comment 5491922231); #7165 remains open — its closure is the PM's, after landing. Clause-②: this widens a published, exactly-pinned context on DataTableSchema (@object-ui/types), ruled at tier in #7188 comment 5502616875 and carried unchanged.

The contract delta

DataTableSchema.renderCellEditor's context, packages/types/src/data-display.ts:

beforeafter
memberscolumn, row, value, stage, commit, cancelcolumn, row, pendingRow, value, stage, commit, cancel
rowthe persisted recordunchanged — the persisted record
pendingRowrow shallow-merged with the row's staged, unsaved edits (its pendingChanges entry); the SAME object as row when nothing is staged
  • Why row was not redefined. Making row mean the merged record would be a silent semantic change to an already-published member — the option that passes review by looking smaller — and a host that needs the persisted value would lose its only source. Both are addressable; neither is redefined. (The card's reasoning; the tier ruling adopted it.)
  • pendingRow is required, not optional, because data-table always passes it — the pin's principle is "transcribed from the call site".
  • Zod mirror (packages/types/src/zod/data-display.zod.ts:270): renderCellEditor: z.function().optional() — it encodes no parameter shape, so there is no ctx member to mirror. The only zod-side delta is the .describe() text, which now records the two-row semantics and names the member on DataTableSchema as the authority. zod-mirror-parity is about key membership and is unaffected.
  • The [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882 exact-shape pin (data-table-declared-keys-6882.test.ts) went red on the seventh member by design and was extended, not weakened: CellEditorContext gains pendingRow; the Equal (not extends) form and the stated reason are kept; a new direction proof _SixMemberShapeIsRefused (@ts-expect-error on the six-member shape) proves the instrument can tell the seventh member's presence from its absence. Leg B of the ablation shows the exact red.

Producer — packages/components/src/renderers/complex/data-table.tsx

injectEditor({ column, row, pendingRow: rowHasChanges ? pendingRows.of(row, rowIndex) : row, value, stage, commit, cancel }). The ruled expression was rowHasChanges ? { ...row, ...rowChanges } : row; the values are identical, but the merged object is cached per row object and rebuilt only when pendingChanges is replaced (every stage / save / cancel builds a new Map). Measured reason: LookupField keys its dependentFilterpopoverFilter memos on the identity of the record it is handed, and its recent-ids useEffect keys on popoverFilter, so a fresh object per render would re-issue that query on every table re-render while the picker is open (useRecordQuery itself is immune — it keys on a JSON filterSignature). row is identity-stable for the same reason. Implementation detail beneath the contract; the member and its semantics are exactly as ruled.

Consumer — packages/plugin-grid/src/ObjectGrid.tsx

The assertion B adds — gridDependentValues-7165.test.tsx, test 4

Edit the parent cell (regionsouth), do not save, open the child picker. Asserts: Person 07 (south) offered, Person 01 (north) not; the query carried $filter.region === 'south' and never 'north'; the region cell renders the pending south while rows[0].region is still north and ds.update was never called (so the staged and persisted parents cannot coincide). Tests 1–3 and the lookupPickerKeys-7154 suite are the live controls — their rows carry no staged edits, so pendingRow and row coincide there; they stay green under both A and B, which is why they cannot see the regression and test 4 can.

Ablation — after commit d20e5c4; both legs restored via git checkout HEAD --, proven by blob hash = HEAD blob and empty git diff HEAD

  • Leg AObjectGrid.tsx back to dependentValues={ctx.row} (mutation proven on disk: B-line count 1→0, A-line 0→1, blob hash ≠ HEAD): Tests 1 failed | 8 passed (9) — exactly test 4 (× 4 — ⭐ objectui#7188: a STAGED parent re-scopes the child…) red; tests 1–3 and all 7154 tests green. No dist rebuild is involved: the root vitest.config.mts aliases @object-ui/types / components / fields / react / plugin-grid to src (lines 281–288), so the suite reads the mutated source directly.
  • Leg B — remove pendingRow: any; from the declared ctx (member count 1→0, blob hash ≠ HEAD): tsc -p tsconfig.test.json in packages/types exits 2 with (127,3) TS2344 on _RenderCellEditorShape, (143,1) TS2578: Unused '@ts-expect-error' directive on _SixMemberShapeIsRefused, and (164,40) TS2339: Property 'pendingRow' does not exist on the authoring example. Direction: red, as predicted; the control directive flips exactly as designed.

Census of renderCellEditor ctx readers

Grep over the whole tree excluding node_modules / dist, control term ctx.row hit (in app-shell/resolveActionParams.ts — a different ctx — and in plugin-grid). examples/, apps/, plugin-dashboard, plugin-detail, app-shell: no reader.

readermembers readeffect of the additive member
packages/plugin-grid/src/ObjectGrid.tsx — the only production consumercolumn, row, value, stage, commit, now pendingRowupdated here
packages/components/src/renderers/complex/data-table.tsxproducer of the ctxupdated here
packages/components/src/__tests__/data-table-injected-editor-focus-6859.test.tsxstagingEditor({ column, value, stage })threeunaffected, green
packages/components/src/__tests__/data-table-inline-edit.test.tsx:192({ column, stage })twounaffected, green
packages/plugin-grid/src/__tests__/dataTableSchemaSlot-6459.test.ts:158(ctx) => ctx.columnone, contextually typed from the seam intersectionunaffected, compiles (the hold copy kept Equal)
packages/types/src/__tests__/data-table-declared-keys-6882.test.tsthe pinextended here
content/docs/components/complex/data-table.mdx:50the documented ctxupdated here

One file outside the dispatched surface — declared

content/docs/components/complex/data-table.mdx (+9/−1): the published documentation of exactly this ctx listed six members. AGENTS.md Commandment #2 (docs reflect the code) is binding, and leaving it would ship documentation that contradicts the type it documents. Drop the hunk if the PM prefers it filed separately.

Verification on d20e5c4 (the pushed head)

  • build: pnpm --filter @object-ui/components --filter @object-ui/plugin-grid build (prebuild rebuilt types / core / react) — green; type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-grid (each tsc --noEmit && tsc -p tsconfig.test.json; types also tsconfig.examples.json) — zero diagnostics; --listFiles confirms the 6882 pin (1) and the 6459 + 7165 files (2) are in their tsconfig.test.json programs. Lock verdict line: os-verify-lock: VERDICT command-exit 0.
  • vitest, root-relative, 7 files (the two grid fixtures, the 6882 pin, the 6459 slot pin, data-table-inline-edit, data-table-injected-editor-focus-6859, inlineEditLookupRepro): Test Files 7 passed (7) / Tests 31 passed (31).
  • gates green: check-changeset-presence (3 changesets for 3 released packages), check-changeset-no-major, check:control-bytes, check:spec-symbols, check:doc-types, check:doc-fences, check-doc-links, check:dist-completeness; eslint on the seven changed ts/tsx files — 0 errors.
  • NOT measured locally (precondition exit 2 — they need the full-farm / console build; CI owns them): check:sdui-registration-pins, check:eager-closure, check:doc-snippets, check:readme-exports. This diff touches no registration and no README, and the data-table.mdx snippet is a plaintext fence.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

…enderCellEditor seam as `pendingRow`
`renderCellEditor`'s context on `DataTableSchema` gains a seventh member,
`pendingRow` — the persisted `row` shallow-merged with the row's staged,
unsaved edits. `row` keeps meaning the persisted record; neither is
redefined. `data-table` builds it from `pendingChanges`, identity-stable per
row while nothing new is staged, so a dependent widget's identity-keyed fetch
effects do not re-fire on every table re-render. `ObjectGrid` scopes a
`dependsOn` inline editor by `ctx.pendingRow ?? ctx.row`, so a parent edited
in the same row re-scopes the child before anything is saved, and the interim
marker that named the follow-up is gone with the interim.
The exact-shape pin in `@object-ui/types` is extended, not weakened, and
gains a control proving it can tell the seventh member's presence from its
absence. Test 4 of the grid fixture flips from pinning the saved-row
staleness to asserting that the staged parent scopes the child, with its own
proof that the staging landed and nothing was saved. The zod mirror encodes
`renderCellEditor` as `z.function()` with no parameter shape; only its
description records the delta. The docs snippet and prose for `data-table`
carry the new member.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.2 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-BDpvv2yk.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.75KB
plugin-grid (index.js)208.88KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.55KB27.68KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-litant
os-litant marked this pull request as ready for review September 2, 2026 01:48
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit adbda1bSep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-7188-pending-row-ctx branch September 2, 2026 02:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow - #7241

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx
Sep 2, 2026
Merged

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow#7241
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7188

Option B of the A/B split ruled on #7165 (comment 5491922231); #7165 remains open — its closure is the PM's, after landing. Clause-②: this widens a published, exactly-pinned context on DataTableSchema (@object-ui/types), ruled at tier in #7188 comment 5502616875 and carried unchanged.

The contract delta

DataTableSchema.renderCellEditor's context, packages/types/src/data-display.ts:

beforeafter
memberscolumn, row, value, stage, commit, cancelcolumn, row, pendingRow, value, stage, commit, cancel
rowthe persisted recordunchanged — the persisted record
pendingRowrow shallow-merged with the row's staged, unsaved edits (its pendingChanges entry); the SAME object as row when nothing is staged
  • Why row was not redefined. Making row mean the merged record would be a silent semantic change to an already-published member — the option that passes review by looking smaller — and a host that needs the persisted value would lose its only source. Both are addressable; neither is redefined. (The card's reasoning; the tier ruling adopted it.)
  • pendingRow is required, not optional, because data-table always passes it — the pin's principle is "transcribed from the call site".
  • Zod mirror (packages/types/src/zod/data-display.zod.ts:270): renderCellEditor: z.function().optional() — it encodes no parameter shape, so there is no ctx member to mirror. The only zod-side delta is the .describe() text, which now records the two-row semantics and names the member on DataTableSchema as the authority. zod-mirror-parity is about key membership and is unaffected.
  • The [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882 exact-shape pin (data-table-declared-keys-6882.test.ts) went red on the seventh member by design and was extended, not weakened: CellEditorContext gains pendingRow; the Equal (not extends) form and the stated reason are kept; a new direction proof _SixMemberShapeIsRefused (@ts-expect-error on the six-member shape) proves the instrument can tell the seventh member's presence from its absence. Leg B of the ablation shows the exact red.

Producer — packages/components/src/renderers/complex/data-table.tsx

injectEditor({ column, row, pendingRow: rowHasChanges ? pendingRows.of(row, rowIndex) : row, value, stage, commit, cancel }). The ruled expression was rowHasChanges ? { ...row, ...rowChanges } : row; the values are identical, but the merged object is cached per row object and rebuilt only when pendingChanges is replaced (every stage / save / cancel builds a new Map). Measured reason: LookupField keys its dependentFilterpopoverFilter memos on the identity of the record it is handed, and its recent-ids useEffect keys on popoverFilter, so a fresh object per render would re-issue that query on every table re-render while the picker is open (useRecordQuery itself is immune — it keys on a JSON filterSignature). row is identity-stable for the same reason. Implementation detail beneath the contract; the member and its semantics are exactly as ruled.

Consumer — packages/plugin-grid/src/ObjectGrid.tsx

The assertion B adds — gridDependentValues-7165.test.tsx, test 4

Edit the parent cell (regionsouth), do not save, open the child picker. Asserts: Person 07 (south) offered, Person 01 (north) not; the query carried $filter.region === 'south' and never 'north'; the region cell renders the pending south while rows[0].region is still north and ds.update was never called (so the staged and persisted parents cannot coincide). Tests 1–3 and the lookupPickerKeys-7154 suite are the live controls — their rows carry no staged edits, so pendingRow and row coincide there; they stay green under both A and B, which is why they cannot see the regression and test 4 can.

Ablation — after commit d20e5c4; both legs restored via git checkout HEAD --, proven by blob hash = HEAD blob and empty git diff HEAD

  • Leg AObjectGrid.tsx back to dependentValues={ctx.row} (mutation proven on disk: B-line count 1→0, A-line 0→1, blob hash ≠ HEAD): Tests 1 failed | 8 passed (9) — exactly test 4 (× 4 — ⭐ objectui#7188: a STAGED parent re-scopes the child…) red; tests 1–3 and all 7154 tests green. No dist rebuild is involved: the root vitest.config.mts aliases @object-ui/types / components / fields / react / plugin-grid to src (lines 281–288), so the suite reads the mutated source directly.
  • Leg B — remove pendingRow: any; from the declared ctx (member count 1→0, blob hash ≠ HEAD): tsc -p tsconfig.test.json in packages/types exits 2 with (127,3) TS2344 on _RenderCellEditorShape, (143,1) TS2578: Unused '@ts-expect-error' directive on _SixMemberShapeIsRefused, and (164,40) TS2339: Property 'pendingRow' does not exist on the authoring example. Direction: red, as predicted; the control directive flips exactly as designed.

Census of renderCellEditor ctx readers

Grep over the whole tree excluding node_modules / dist, control term ctx.row hit (in app-shell/resolveActionParams.ts — a different ctx — and in plugin-grid). examples/, apps/, plugin-dashboard, plugin-detail, app-shell: no reader.

readermembers readeffect of the additive member
packages/plugin-grid/src/ObjectGrid.tsx — the only production consumercolumn, row, value, stage, commit, now pendingRowupdated here
packages/components/src/renderers/complex/data-table.tsxproducer of the ctxupdated here
packages/components/src/__tests__/data-table-injected-editor-focus-6859.test.tsxstagingEditor({ column, value, stage })threeunaffected, green
packages/components/src/__tests__/data-table-inline-edit.test.tsx:192({ column, stage })twounaffected, green
packages/plugin-grid/src/__tests__/dataTableSchemaSlot-6459.test.ts:158(ctx) => ctx.columnone, contextually typed from the seam intersectionunaffected, compiles (the hold copy kept Equal)
packages/types/src/__tests__/data-table-declared-keys-6882.test.tsthe pinextended here
content/docs/components/complex/data-table.mdx:50the documented ctxupdated here

One file outside the dispatched surface — declared

content/docs/components/complex/data-table.mdx (+9/−1): the published documentation of exactly this ctx listed six members. AGENTS.md Commandment #2 (docs reflect the code) is binding, and leaving it would ship documentation that contradicts the type it documents. Drop the hunk if the PM prefers it filed separately.

Verification on d20e5c4 (the pushed head)

  • build: pnpm --filter @object-ui/components --filter @object-ui/plugin-grid build (prebuild rebuilt types / core / react) — green; type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-grid (each tsc --noEmit && tsc -p tsconfig.test.json; types also tsconfig.examples.json) — zero diagnostics; --listFiles confirms the 6882 pin (1) and the 6459 + 7165 files (2) are in their tsconfig.test.json programs. Lock verdict line: os-verify-lock: VERDICT command-exit 0.
  • vitest, root-relative, 7 files (the two grid fixtures, the 6882 pin, the 6459 slot pin, data-table-inline-edit, data-table-injected-editor-focus-6859, inlineEditLookupRepro): Test Files 7 passed (7) / Tests 31 passed (31).
  • gates green: check-changeset-presence (3 changesets for 3 released packages), check-changeset-no-major, check:control-bytes, check:spec-symbols, check:doc-types, check:doc-fences, check-doc-links, check:dist-completeness; eslint on the seven changed ts/tsx files — 0 errors.
  • NOT measured locally (precondition exit 2 — they need the full-farm / console build; CI owns them): check:sdui-registration-pins, check:eager-closure, check:doc-snippets, check:readme-exports. This diff touches no registration and no README, and the data-table.mdx snippet is a plaintext fence.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

…enderCellEditor seam as `pendingRow`
`renderCellEditor`'s context on `DataTableSchema` gains a seventh member,
`pendingRow` — the persisted `row` shallow-merged with the row's staged,
unsaved edits. `row` keeps meaning the persisted record; neither is
redefined. `data-table` builds it from `pendingChanges`, identity-stable per
row while nothing new is staged, so a dependent widget's identity-keyed fetch
effects do not re-fire on every table re-render. `ObjectGrid` scopes a
`dependsOn` inline editor by `ctx.pendingRow ?? ctx.row`, so a parent edited
in the same row re-scopes the child before anything is saved, and the interim
marker that named the follow-up is gone with the interim.
The exact-shape pin in `@object-ui/types` is extended, not weakened, and
gains a control proving it can tell the seventh member's presence from its
absence. Test 4 of the grid fixture flips from pinning the saved-row
staleness to asserting that the staged parent scopes the child, with its own
proof that the staging landed and nothing was saved. The zod mirror encodes
`renderCellEditor` as `z.function()` with no parameter shape; only its
description records the delta. The docs snippet and prose for `data-table`
carry the new member.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.2 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-BDpvv2yk.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.75KB
plugin-grid (index.js)208.88KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.55KB27.68KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-litant
os-litant marked this pull request as ready for review September 2, 2026 01:48
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit adbda1bSep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-7188-pending-row-ctx branch September 2, 2026 02:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow - #7241

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx
Sep 2, 2026
Merged

feat(types,components,plugin-grid): carry the staged row across the renderCellEditor seam as pendingRow#7241
os-litant merged 1 commit into
mainfrom
claude/issue-7188-pending-row-ctx

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7188

Option B of the A/B split ruled on #7165 (comment 5491922231); #7165 remains open — its closure is the PM's, after landing. Clause-②: this widens a published, exactly-pinned context on DataTableSchema (@object-ui/types), ruled at tier in #7188 comment 5502616875 and carried unchanged.

The contract delta

DataTableSchema.renderCellEditor's context, packages/types/src/data-display.ts:

beforeafter
memberscolumn, row, value, stage, commit, cancelcolumn, row, pendingRow, value, stage, commit, cancel
rowthe persisted recordunchanged — the persisted record
pendingRowrow shallow-merged with the row's staged, unsaved edits (its pendingChanges entry); the SAME object as row when nothing is staged
  • Why row was not redefined. Making row mean the merged record would be a silent semantic change to an already-published member — the option that passes review by looking smaller — and a host that needs the persisted value would lose its only source. Both are addressable; neither is redefined. (The card's reasoning; the tier ruling adopted it.)
  • pendingRow is required, not optional, because data-table always passes it — the pin's principle is "transcribed from the call site".
  • Zod mirror (packages/types/src/zod/data-display.zod.ts:270): renderCellEditor: z.function().optional() — it encodes no parameter shape, so there is no ctx member to mirror. The only zod-side delta is the .describe() text, which now records the two-row semantics and names the member on DataTableSchema as the authority. zod-mirror-parity is about key membership and is unaffected.
  • The [Decision] Declare renderCellEditor and schema-level cellClassName on DataTableSchema? — the two live undeclared keys the #6459 census measured #6882 exact-shape pin (data-table-declared-keys-6882.test.ts) went red on the seventh member by design and was extended, not weakened: CellEditorContext gains pendingRow; the Equal (not extends) form and the stated reason are kept; a new direction proof _SixMemberShapeIsRefused (@ts-expect-error on the six-member shape) proves the instrument can tell the seventh member's presence from its absence. Leg B of the ablation shows the exact red.

Producer — packages/components/src/renderers/complex/data-table.tsx

injectEditor({ column, row, pendingRow: rowHasChanges ? pendingRows.of(row, rowIndex) : row, value, stage, commit, cancel }). The ruled expression was rowHasChanges ? { ...row, ...rowChanges } : row; the values are identical, but the merged object is cached per row object and rebuilt only when pendingChanges is replaced (every stage / save / cancel builds a new Map). Measured reason: LookupField keys its dependentFilterpopoverFilter memos on the identity of the record it is handed, and its recent-ids useEffect keys on popoverFilter, so a fresh object per render would re-issue that query on every table re-render while the picker is open (useRecordQuery itself is immune — it keys on a JSON filterSignature). row is identity-stable for the same reason. Implementation detail beneath the contract; the member and its semantics are exactly as ruled.

Consumer — packages/plugin-grid/src/ObjectGrid.tsx

The assertion B adds — gridDependentValues-7165.test.tsx, test 4

Edit the parent cell (regionsouth), do not save, open the child picker. Asserts: Person 07 (south) offered, Person 01 (north) not; the query carried $filter.region === 'south' and never 'north'; the region cell renders the pending south while rows[0].region is still north and ds.update was never called (so the staged and persisted parents cannot coincide). Tests 1–3 and the lookupPickerKeys-7154 suite are the live controls — their rows carry no staged edits, so pendingRow and row coincide there; they stay green under both A and B, which is why they cannot see the regression and test 4 can.

Ablation — after commit d20e5c4; both legs restored via git checkout HEAD --, proven by blob hash = HEAD blob and empty git diff HEAD

  • Leg AObjectGrid.tsx back to dependentValues={ctx.row} (mutation proven on disk: B-line count 1→0, A-line 0→1, blob hash ≠ HEAD): Tests 1 failed | 8 passed (9) — exactly test 4 (× 4 — ⭐ objectui#7188: a STAGED parent re-scopes the child…) red; tests 1–3 and all 7154 tests green. No dist rebuild is involved: the root vitest.config.mts aliases @object-ui/types / components / fields / react / plugin-grid to src (lines 281–288), so the suite reads the mutated source directly.
  • Leg B — remove pendingRow: any; from the declared ctx (member count 1→0, blob hash ≠ HEAD): tsc -p tsconfig.test.json in packages/types exits 2 with (127,3) TS2344 on _RenderCellEditorShape, (143,1) TS2578: Unused '@ts-expect-error' directive on _SixMemberShapeIsRefused, and (164,40) TS2339: Property 'pendingRow' does not exist on the authoring example. Direction: red, as predicted; the control directive flips exactly as designed.

Census of renderCellEditor ctx readers

Grep over the whole tree excluding node_modules / dist, control term ctx.row hit (in app-shell/resolveActionParams.ts — a different ctx — and in plugin-grid). examples/, apps/, plugin-dashboard, plugin-detail, app-shell: no reader.

readermembers readeffect of the additive member
packages/plugin-grid/src/ObjectGrid.tsx — the only production consumercolumn, row, value, stage, commit, now pendingRowupdated here
packages/components/src/renderers/complex/data-table.tsxproducer of the ctxupdated here
packages/components/src/__tests__/data-table-injected-editor-focus-6859.test.tsxstagingEditor({ column, value, stage })threeunaffected, green
packages/components/src/__tests__/data-table-inline-edit.test.tsx:192({ column, stage })twounaffected, green
packages/plugin-grid/src/__tests__/dataTableSchemaSlot-6459.test.ts:158(ctx) => ctx.columnone, contextually typed from the seam intersectionunaffected, compiles (the hold copy kept Equal)
packages/types/src/__tests__/data-table-declared-keys-6882.test.tsthe pinextended here
content/docs/components/complex/data-table.mdx:50the documented ctxupdated here

One file outside the dispatched surface — declared

content/docs/components/complex/data-table.mdx (+9/−1): the published documentation of exactly this ctx listed six members. AGENTS.md Commandment #2 (docs reflect the code) is binding, and leaving it would ship documentation that contradicts the type it documents. Drop the hunk if the PM prefers it filed separately.

Verification on d20e5c4 (the pushed head)

  • build: pnpm --filter @object-ui/components --filter @object-ui/plugin-grid build (prebuild rebuilt types / core / react) — green; type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-grid (each tsc --noEmit && tsc -p tsconfig.test.json; types also tsconfig.examples.json) — zero diagnostics; --listFiles confirms the 6882 pin (1) and the 6459 + 7165 files (2) are in their tsconfig.test.json programs. Lock verdict line: os-verify-lock: VERDICT command-exit 0.
  • vitest, root-relative, 7 files (the two grid fixtures, the 6882 pin, the 6459 slot pin, data-table-inline-edit, data-table-injected-editor-focus-6859, inlineEditLookupRepro): Test Files 7 passed (7) / Tests 31 passed (31).
  • gates green: check-changeset-presence (3 changesets for 3 released packages), check-changeset-no-major, check:control-bytes, check:spec-symbols, check:doc-types, check:doc-fences, check-doc-links, check:dist-completeness; eslint on the seven changed ts/tsx files — 0 errors.
  • NOT measured locally (precondition exit 2 — they need the full-farm / console build; CI owns them): check:sdui-registration-pins, check:eager-closure, check:doc-snippets, check:readme-exports. This diff touches no registration and no README, and the data-table.mdx snippet is a plaintext fence.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

…enderCellEditor seam as `pendingRow`
`renderCellEditor`'s context on `DataTableSchema` gains a seventh member,
`pendingRow` — the persisted `row` shallow-merged with the row's staged,
unsaved edits. `row` keeps meaning the persisted record; neither is
redefined. `data-table` builds it from `pendingChanges`, identity-stable per
row while nothing new is staged, so a dependent widget's identity-keyed fetch
effects do not re-fire on every table re-render. `ObjectGrid` scopes a
`dependsOn` inline editor by `ctx.pendingRow ?? ctx.row`, so a parent edited
in the same row re-scopes the child before anything is saved, and the interim
marker that named the follow-up is gone with the interim.
The exact-shape pin in `@object-ui/types` is extended, not weakened, and
gains a control proving it can tell the seventh member's presence from its
absence. Test 4 of the grid fixture flips from pinning the saved-row
staleness to asserting that the staged parent scopes the child, with its own
proof that the staging landed and nothing was saved. The zod mirror encodes
`renderCellEditor` as `z.function()` with no parameter shape; only its
description records the delta. The docs snippet and prose for `data-table`
carry the new member.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.2 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-BDpvv2yk.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.75KB
plugin-grid (index.js)208.88KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.55KB27.68KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-litant
os-litant marked this pull request as ready for review September 2, 2026 01:48
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit adbda1bSep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-7188-pending-row-ctx branch September 2, 2026 02:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude