Skip to content

fix(components,plugin-grid): the row menu asks the action family whether a visible gate is declared (objectui#11294) - #11319

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-11294-row-menu-declared-gate
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-11294-row-menu-declared-gate

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11294
Clause-②: yes (widening)

What changed

A related list's row menu now gives the same answer as its toolbar to "is a visible gate declared?". An action authored visible: ' ' shows on the toolbar and on the rows, whether the rows reach it through record_related or list_item.

  • @object-ui/components: isCustomRowActionVisible (the data table's row menu) asks hasDeclaredVisibilityGate, the action family's one definition, instead of its own pred == null || pred === ''. A blank visible ('', whitespace, or an envelope whose source is blank) is no gate, so the action shows. It is still reported once through the family's [blank] report (ADR-0137 D4). The function takes the object's field definitions as an optional fourth argument and is now exported from the package barrel, next to hasDeclaredVisibilityGate.
  • @object-ui/plugin-grid: RowActionMenu's twin of isCustomRowActionVisible is deleted. Its inline primary buttons, its "⋮" items and its "⋮" guard read the one function from @object-ui/components. plugin-grid already depends on @object-ui/components, so there is no new dependency edge and no cycle (components does not depend on plugin-grid).
  • No third "declared?" test is added, and the toolbar gate (objectui#11244) and the row placement (objectui#11270) are untouched.

Export change, for the ACCEPT to re-read (as the claim asked): @object-ui/components gains one named export, isCustomRowActionVisible(action, row, scope, fields?). It was already exported from its module by deep path, for tests. plugin-grid's copy was never in its barrel, so nothing public leaves.

The two definitions, as measured before the change (Zone 2.2)

  • The family's: hasDeclaredVisibilityGate lives in @object-ui/components (renderers/action/visibility-gate.ts) as a re-export of core's hasDeclaredPredicate. Blank text, whitespace, a blank-source envelope and junk are "not declared". It reports each blank once with the context "a declared gate, read as no gate".
  • The row menu's: both isCustomRowActionVisible bodies returned true only for pred == null || pred === ''. Anything else went to evalRowPredicate with fallback: false, so a blank failed closed. The two bodies differed only in plugin-grid's extra fields argument.

Old and new row verdicts, measured with a throwaway probe (deleted), row { x: 1 }:

visible old row verdict new row verdict
' ' hidden shown
'' shown shown
{ dialect: 'cel', source: '' } hidden shown
{ dialect: 'cel', source: ' ' } hidden shown
0, {}, [] (not a predicate) hidden shown
false hidden hidden
true, 'record.x == 1' shown shown

The junk row moves too. That is the family's rule ("fail-open on junk"), and the changesets say so.

The [blank] report (Zone 2.4)

Measured on the base through the real block: the toolbar reported ' ' and '' once each, through the family's channel. The row path reported ' ' once per action, labelled with the action name, on the fail-closed route. It said nothing for ''. After the change, the row path reports through the same family channel as the toolbar. That channel is deduplicated per blank spelling across the app, so the report is not repeated per row or per render. The pin below shows this with a blank used only on the rows (two rows, one report). The dedupe is per blank spelling, not per action: two actions with the same blank spelling give one line, as on the toolbar. That is the documented price of core's fixed locator, and this PR leaves it as it is.

Pins

  • packages/app-shell/src/views/__tests__/RelatedRecordActionsBridge.blankVisible-11294.test.tsx: the card's table through the real RelatedRecordActionsBridge, the record:related_list node through the real SchemaRenderer, plugin-detail's registration, the real RelatedList and the real data table, at desktop width. The toolbar shows blank_rr, blank_li, empty_rr and empty_li. The opened row menu shows the same four beside the ungated send_reminder control. never_li (visible: false) stays hidden on both. CEL gates are evaluated against the row: cel_yes_rr shows and cel_no_rr does not. A rows-only blank is reported exactly once.
  • packages/components/.../data-table-row-action-visible.test.tsx: the item and the planDataTableRowMenu guard, for whitespace, a whitespace-source envelope and an empty-source envelope.
  • packages/plugin-grid/.../RowActionMenu.test.tsx: the inline primary button, the "⋮" guard (the action is alone on the row, so the trigger exists only if the guard counted it) and the "⋮" item.
  • predicate-surface-parity.test.tsx now imports the function from @object-ui/components. Its cases are unchanged.

Evidence

All test commands ran from the repo root through os-verify-lock.sh. The reruns are at head cb23049281.

  • Red before (Zone 2.1): at base caa0cd3927, the new real-block pin failed with expected [ 'send_reminder', 'empty_rr', …(2) ] to deeply equal [ 'send_reminder', 'blank_rr', …(4) ]: blank_rr and blank_li were absent from the row menu. The toolbar assertion passed. This reproduces the card's table on current main.
  • Ablation (ablation-replace.mjs, wrap mode): the shared helper's if (!hasDeclaredVisibilityGate(pred)) return true; was swapped back to the old test, anchor 1 -> 0, blob de5ae999a55b -> 5564ded0538a. Result: Tests 10 failed | 30 passed (40). All 10 new cases were red, across the three pin files. plugin-grid's pins went red from a mutation in @object-ui/components alone, which shows that plugin-grid reads the one function. Then ok restored: blob == HEAD (de5ae999a55b) and git diff HEAD is empty.
  • Reverse type check: a fifth argument in plugin-grid's guard call made tsc fail with error TS2554: Expected 3-4 arguments, but got 5., so plugin-grid reads the rebuilt .d.ts. Restored, blob == HEAD.
  • Build: pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-grid^...' build exited 0. --filter '@object-ui/app-shell^...' build exited 0.
  • Type-check: @object-ui/components, @object-ui/plugin-grid, @object-ui/plugin-detail and @object-ui/app-shell each ran tsc --noEmit && tsc -p tsconfig.test.json with exit 0. --listFiles shows that each test program reads the touched test files.
  • Tests:
    • pnpm exec vitest run --maxWorkers=2 packages/components/src/renderers/complex/ packages/plugin-grid/: Test Files 199 passed (199), Tests 1794 passed (1794).
    • packages/plugin-detail/: Test Files 228 passed | 1 skipped (229), Tests 2270 passed | 8 skipped (2278).
    • Every other suite that git grep -l finds for isCustomRowActionVisible or hasDeclaredVisibilityGate, plus the objectui#11270 bridge suite: Test Files 13 passed (13), Tests 262 passed (262).
  • Repo checks, all exit 0:
    • check:new-line-citations: VERDICT new-cross-file-line-citations: 0 new citation(s).
    • check-changeset-presence.mjs: 7 source file(s) of 3 released package(s) changed, and this change declares 2 changeset(s).
    • changeset:check: no major.
    • check:control-bytes: OK.
    • check:changeset-claims, check:pending-changeset-literals, check:test-path-roots and check:action-forward-parity also passed.
  • Lint, a declared narrowing. CI's Lint job owns the full run. Here eslint --format json ran on the 7 touched .ts/.tsx files: files 7 errors 0 warnings 67. ① The population was read from eslint's own config: eslint --print-config resolves a config for each of the 7, so none is ignored. ② The file count, 7, comes from the JSON output. ③ Invariance: eslint.config.js enables no type-aware linting (no projectService, no parserOptions.project), and no rule in eslint-rules/ reads another file from disk. So this diff cannot move the verdict for any file it does not touch. Against the base, the same 6 pre-existing files went from 68 warnings to 67; the 7th is the new pin file, with none. Net change −1 warning and 0 errors: +1 no-explicit-any in the new guard pin, −2 in RowActionMenu.tsx with the twin removed.

Acceptance notes

  • Same family, left as it is (for the objectui#3812 declared-gate close-out). The grid's selection bar still answers "declared?" with its own != null && !== '': plugin-grid's bulkEligibility hasVisibilityGate, with core's partitionRowsByPredicate opening the same way. Measured through the real BulkActionBar with one selected row: visible: ' ' and a whitespace-source envelope hide the bulk button, while '' and an ungated def show it. The row kebab now shows all four. So for a whitespace visible, the bar is now the one surface that disagrees with the toolbars and the rows. Before this PR it agreed with the rows and disagreed with the toolbars. The objectui#3521 parity table therefore still has no whitespace row. This PR does not touch the bulk files, which are outside this card's surface.
  • The built-in Edit/Delete visibleWhen keeps its != null rule on both row menus. Read at source and not measured: the record header's Edit/Delete (RecordDetailView, useRowPredicate with fallback: false) also hide on a blank, so no surface appears to disagree there. It is a field-rule key under ADR-0137, outside this card's family.

Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ (domain:ui seat 1 dispatch, os-dev).


Generated by Claude Code

claude added 2 commits October 1, 2026 02:27
…her a `visible` gate is declared

`isCustomRowActionVisible` (the related list's data table) and its twin in
plugin-grid's `RowActionMenu` asked "is a gate declared?" with a test of their
own, `pred == null || pred === ''`. A whitespace-only `visible` counted as
declared, was evaluated, and failed closed: the action showed in a related
list's toolbar and was missing from its rows, for `record_related` and
`list_item` alike.

The data table's function now asks `hasDeclaredVisibilityGate`, the family's
one definition, takes the object's fields as an optional argument, and is
exported from the `@object-ui/components` barrel. plugin-grid's row menu
imports it and its twin is gone, so one function serves both row menus.

Pins: the card's table through the real `record:related_list` block under the
real bridge (app-shell), the data table's item and guard, and plugin-grid's
inline button, item and guard.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
…ents and plugin-grid

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

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

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

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

.changeset/4730-retire-dead-locale-key-batch.md

  • names RowActionMenu.tsx → packages/plugin-grid/src/components/RowActionMenu.tsx — edited by this change

    • Superseded twin vocabularies. cellRender.* and rowAction.* duplicated a grid.* vocabulary that won. RowActionMenu.tsx is fully i18n-wired and reads grid.openMenu / grid.edit / grid.delete; ObjectGrid.tsx reads grid.empty / grid.yes / grid.no / grid.systemFields. The twins had no reader on either side. - Labels that outlived their control. calendar.agenda labelled a view mode b55a34647 retired from CalendarViewMode (now 'month' | 'week' | 'day'). home.quickActions.createApp*, layout.systemNav.createApp, actionDialog.defaultActionTitle / .ok and grid.bulk.selectPlaceholder sit in namespaces whose consumers are live and wired but demonstrably read other siblings. - Surfaces that left the product. map.* is the strongest form: @object-ui/plugin-map declares no @object-ui/i18n dependency and contains no t() call at all, so it cannot consume a locale string. home.stats.* and recordDetail.viewersTooltip name surfaces nothing renders.

.changeset/5453-retire-grid-column-wrap-forward.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    generateColumns() copied a per-column wrap onto every column object handed to data-table, and packages/components' data-table.tsx never read it. Measured on the current ref rather than inherited from the card, with comments stripped so prose mentions cannot be counted as reads: a column-level wrap scores 0, against accessorKey 34, align 5, header 4, className 4, width 8 and fitContent 2 in the same query shape. Those sibling counts are the positive control — the search style does find the keys that are genuinely consumed, so the zero is a measurement and not a mis-aimed grep. The raw string wrap does occur in that file; every occurrence is flex-wrap, whitespace-nowrap, or a variable named wrapper.

.changeset/5853-tablecolumn-type-canonical-union.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    @object-ui/types exports TABLE_COLUMN_TYPES (the canonical tuple — the single declaration the zod mirror builds its enum from, so the two cannot drift), the TableColumnType union, and normalizeTableColumnType() for producers. The as any cast in data-table.tsx is deleted and the read is typed, so re-introducing an undeclared spelling is a tsc error rather than a silent widening.

.changeset/5926-empty-action-visible-when.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    The fix is a route, not a new check: no visibleWhen test was added to data-table.tsx. A local check on this slot would have been a fourth evaluator, which is the drift page:tabs' item-level predicate already records on this card. The slot now mounts its authored node exactly the way the empty renderer's action slot always has.

.changeset/5993-button-shared-icon-resolver.md

.changeset/6175-column-state-persistence.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    • @object-ui/components — DataTableSchema has declared onColumnResize?: (columnKey, width) => void all along, and data-table.tsx invoked it nowhere: the resize drag updated the table's local columnWidths state and stopped there. It now reports the settled width once, at mouseup. Once, deliberately — the host turns this callback into a write to shared view config, so a per-mousemove callback would be a write storm. - @object-ui/plugin-grid — ObjectGrid emitted onColumnReorder (singular) while the renderer invokes the near-duplicate onColumnsReorder (with the s), a different declared key with a different signature. The producer now emits the spelling the renderer actually invokes, mapping the reported TableColumn[] to the accessorKey order columnState stores.

.changeset/6373-datatable-emit-boundary.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    The measured read set of the consumer (data-table.tsx, comments stripped) contains none of the six, so all six retire from the emit rather than being declared — declaring a key nothing reads is the same declared != enforced defect facing the other way. Rendering is unchanged because none of those keys was the live path for its own value: the FieldMeta the cell closure captures is what this widget's type-aware rendering has always read, and it is untouched. Authored spellings still pass through, so a column the author wrote as { format: '$0,0' } keeps its format exactly as before.

.changeset/6424-tablecolumn-declares-headericon.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Scope of the enforcement claim, measured: the declaration, the parse road, and the renderer's behaviour now agree; the renderer's internal column reads remain any-mediated (the col: any normalization in data-table.tsx) — a standing instrument gap, not closed here.

.changeset/6881-retire-data-table-toolbar.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    What was measured. The key was declared on both published faces — data-display.ts (toolbar?: SchemaNode[], "Table toolbar actions/content") and the Zod mirror (SchemaNode | SchemaNode[]) — documented, mirrored, and read by NOTHING: data-table.tsx, the registered renderer for type: 'data-table', contains the word only in two prose comments and never reads schema.toolbar. The sibling emptyAction slot on the same interface IS mounted through SchemaRenderer, so the census zero is a reading, not a blind query. An author who wrote a toolbar got a green document and a blank result, with no signal anywhere that said so — the declared-vs-enforced failure mode that is worst for AI-authored metadata, which has nothing but the declaration to go on.

.changeset/6882-datatable-declare-render-cell-editor-and-cell-classname.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    data-table has read both keys on its production path all along — renderCellEditor through a (schema as any) cast, cellClassName by destructuring it into the class of its three utility cells (the selection checkbox, the row number, the row actions). Neither was declared, so authoring either one was unchecked: a misspelling produced no error and no widget, and no editor completion offered them. DataTableSchema now declares both, and the cast in data-table.tsx is gone rather than replaced.

.changeset/6909-fieldeditwidget-dom-pass-through.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Also corrects a comment in @object-ui/components' data-table.tsx that this change falsifies. It justified the injected editor's document-level pointerdown listener partly with "FieldEditWidget forwards autoFocus and nothing else out of the DOM block, so a host handler could not reach the control through it even if one were passed" — no longer true. The listener is still load-bearing for the other half of that reason, which is untouched: the renderCellEditor context object has nowhere to put an onBlur in the first place. Comment only; no behaviour change in that package.

.changeset/7804-data-table-handler-key-arms.md

  • names renderers/complex/data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    BaseSchema is .passthrough(), so a key no arm declares is not refused — it stops being judged and the value is KEPT. All seven were in that state while renderers/complex/data-table.tsx read and INVOKED each one, so an authored { "type": "data-table", "onRowClick": { "action": "toast" } } parsed GREEN and that action object was handed to a call site expecting a function. Each key is now a named refusal on the mirror (handlerKeyRefusal, the objectui#6124 shape), and the arm's message says why JSON cannot author it and what to write instead.

.changeset/dashboard-stable-empty-rows-4629.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Nothing rendered wrong before or after; this is wasted work in the empty window, plus the live react-hooks/exhaustive-deps warning the conditional raised. It is the same module-scope frozen empty data-table.tsx adopted for its own EMPTY_ROWS (objectui#4618), applied to the provider: 'object' siblings.

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

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

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

Compared the checked-out tree with 0c6f9bbd7 (merge-base with origin/main): 7 file(s) changed outside .changeset/, read against 1885 pending declaration(s) that publish a body (2498 pending in total). · run

@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3585.7 KB 3607.4 KB
Main entry chunk (gzip) 150.4 KB 350 KB
Entry file index-c7d3WjaY.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 571.63KB 136.90KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 229.96KB 63.80KB
fields (index.js) 261.05KB 66.23KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.95KB 11.48KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 52.90KB 15.35KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 139.34KB 37.30KB
plugin-designer (index.js) 216.45KB 44.60KB
plugin-detail (index.js) 244.79KB 64.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 173.70KB 44.63KB
plugin-gantt (index.js) 173.03KB 43.07KB
plugin-grid (index.js) 231.53KB 63.58KB
plugin-kanban (index.js) 49.32KB 15.48KB
plugin-list (index.js) 116.63KB 28.97KB
plugin-map (index.js) 23.54KB 7.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 33.05KB 9.67KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 21.59KB 7.71KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: cb230492819eeb146d656ee87827c725307b5f8e
Local-runs: none

Inputs, and nothing else: card objectui#11294 (body; triage 5922536242; claim 5923188858; dev report 5923904339), PR objectui#11319 (body; /files: 9 files, +449 / −70; the net diff git diff caa0cd3927 cb23049281, where caa0cd3927 is the head's merge-base with origin/main: 2 commits, 18ebe6d47d fix and cb23049281 changesets), and the head's check-runs, read twice. Read-only context: PR objectui#11275 (objectui#11244), PR objectui#11289 (objectui#11270), objectui#3812, objectui#3521, the objectstack rulebook (AGENTS.md Post-Task #3, the contract-review reference). Not the dispatch order, not the seat's conclusions. Nothing built, run or re-run; the dev's red-first, ablation, type-check and suite figures are quoted from the report where named and are not re-derived. origin/main has not moved on any touched file since caa0cd3927 (git diff --stat empty); the PR is behind main cd5b19a7e7 and mergeable.

① Derived judgments

  1. One shared row helper, no third test — right. At the head isCustomRowActionVisible is defined once, packages/components/src/renderers/complex/data-table.tsx:300–309, and its declaredness line is if (!hasDeclaredVisibilityGate(pred)) return true; (:307), the family's re-export of core hasDeclaredPredicate (renderers/action/visibility-gate.ts, core/src/evaluator/declaredPredicate.ts). plugin-grid's twin (base RowActionMenu.tsx:241–250) is deleted; RowActionMenu.tsx:24 imports the one function from @object-ui/components, and all three plugin-grid call sites read it (:334 the "⋮" guard, :396 the item, :506 the inline button), each passing objectFields as the new optional fourth argument. git grep at the head finds no other isCustomRowActionVisible body. The two remaining pred == null || pred === '' lines (data-table.tsx:228, RowActionMenu.tsx:189) sit inside evalRowActionVisibility, the fail-closed evaluator leg reached only after "declared?" was answered (for a '' arriving from the built-in visibleWhen path, whose gate is != null); they decide a verdict, not declaredness, and both comments now say so. This is the triage's "If one shared row helper can replace the twins, prefer it", and objectui#11270's scope item 3 is met by it. Cosmetic, not a second definition: data-table.tsx now imports the same function under two names (hasDeclaredVisibilityGate at :13, hasDeclaredPredicate from core at :16, used at :414 for disabled).
  2. Dependency direction — right. packages/plugin-grid/package.json already lists @object-ui/components: workspace:*; @object-ui/components' workspace deps are core, i18n, react, react-runtime, sdui-parser, types — no edge back. Pre-Install Import Graph Check concluded success.
  3. The accept-set move on the rows — right, and disclosed. Derived from hasDeclaredPredicate: isBlankPredicateText folds ' ' and { source: ' ' } to "not declared" (reported), toPredicateInput folds '', { source: '' }, 0, {} and an array to undefined (silent). So on both row menus: '' shown → shown (the control); whitespace and a blank-source envelope hidden → shown; false hidden → hidden; a CEL string still evaluated fail-closed; junk (0, {}, []) hidden → shown. That is the toolbar's behaviour since PR objectui#11275 (plugin-detail/src/RelatedList.tsx:457, hasDeclaredVisibilityGate(visiblePred) && !isVisible) and the helper's own docblock table. Both changesets name the blank move, the false and CEL legs, and the junk move ("A value that is not a predicate at all (0, {}) is no gate … It used to hide the action"); [] is covered by that sentence and by the PR's table. The pins separate "no gate" from "always show": never_li (false) stays hidden on both surfaces, cel_no_rr is hidden, cel_yes_rr shown.
  4. The [blank] report — right. New path: hasDeclaredPredicate → reportBlankGate → evalFieldPredicate(value, {}, false, undefined, undefined, { context: 'a declared gate, read as no gate' }) → warnPredicateFailure, whose key for blank text is JSON.stringify([dialect, source, context]) with no field locator (core/src/evaluator/fieldRules.ts:158–161): one line per blank spelling across the app, the toolbar's channel exactly. Old path (base): evalRowPredicate(pred, row, { label: action name }) on the fail-closed route → once per action, labelled; '' never reported (short-circuited by pred === ''). The dev's description matches source. The cost is disclosed in the PR and both changesets: per spelling, not per action, so two actions with one blank spelling give one unlabelled line — the family's documented price (declaredPredicate.ts, "The locator is FIXED"). The pin's count filter ('[blank]' plus JSON.stringify(' ')) is sound: the warning text carries JSON.stringify(expr.source) (fieldRules.ts:166).
  5. Pins through the real block, red-first — right, one caveat. app-shell/src/views/__tests__/RelatedRecordActionsBridge.blankVisible-11294.test.tsx mounts the real RelatedRecordActionsBridge, the record:related_list node through the real SchemaRenderer with @object-ui/plugin-detail registered, the real RelatedList and data table at 1280 px, and asserts the card's table on the toolbar and the opened row menu (blank_rr / blank_li by record_related / list_item, empty_* the control), plus false, two CEL rows and a rows-only blank reported once. Both PR objectui#11275 (e420df310f) and PR objectui#11289 (a8b9889332) are ancestors of caa0cd3927, so at the base the toolbar leg passes and the row leg fails — the quoted red ([ 'send_reminder', 'empty_rr', …(2) ] against [ 'send_reminder', 'blank_rr', …(4) ]) is the predicted shape. Caveat, as the report states: the red run predates blank_row_only and its count assertion; the final pin's sensitivity rests on the ablation below. The unit pins cover the item and the planDataTableRowMenu guard (components, 3 spellings × 2) and the inline button, "⋮" guard and item (plugin-grid, 3).
  6. Ablation and reverse type check — right (quoted). Mutating the one anchor in data-table.tsx back to the old test reddened 10 failed | 30 passed (40): 1 (app-shell) + 6 (components) + 3 (plugin-grid) is exactly the diff's new cases, and plugin-grid's three going red from a components-only mutation is what shows the single definition (vitest resolves @object-ui/components to source). Restore proven (blob de5ae999a55b == HEAD, matching the diff's index line). A planted fifth argument failing with TS2554: Expected 3-4 arguments matches the signature (action, row, scope, fields?).
  7. Public surface — the dev's report is right; the derived Clause-② is wrong. @object-ui/components' exports map exposes . (dist/index.d.ts, dist/index.js) and ./style.css only, so the base's export function isCustomRowActionVisible in data-table.tsx was reachable only by a deep path the map does not address: shipped bytes, not published accept set (contract-review reference: the published surface is the exports map; an entry-unreachable .d.ts is not one). index.ts:70 makes the symbol reachable from the entry type graph: ONE new named export, isCustomRowActionVisible(action, row, scope, fields?), on a released package — a widening. plugin-grid: its barrel never re-exported RowActionMenu (base and head src/index.ts grep empty), so the deleted twin leaves nothing public. The dev reported the export change in the PR body and the report, as the claim asked; what follows from it is in ②.
  8. Scope held — right. RelatedList.tsx (the objectui#11244 toolbar gate) and RelatedRecordActionsBridge.tsx (the objectui#11270 placement) are not in the file list; the bulk files are untouched. Doc comments in the two row-menu files and the three touched test headers were rewritten to the new rule (no stale "!= null && !== ''" claim remains on the custom path).

② Semver level

Changesets: .changeset/11294-components-row-menu-declared-gate.md '@object-ui/components': patch; .changeset/11294-plugin-grid-row-menu-declared-gate.md '@object-ui/plugin-grid': patch; app-shell carries a test only, and Changeset Declaration (check-changeset-presence: "7 source file(s) of 3 released package(s) changed … 2 changeset(s)") concluded success, so no app-shell changeset is owed. PR body line 2: Clause-②: no.

  • @object-ui/plugin-grid patch — right. A verdict fix on an already-accepted value; its public surface loses nothing (① item 7).
  • @object-ui/components patch and Clause-②: no — wrong. The diff publishes a new named export on the package entry (① item 7). By the fleet's reading a new exported symbol is a Clause-② carrier, and objectstack AGENTS.md Post-Task Add public roadmap, VitePress documentation site, and GitHub Pages deployment #3 makes yes take at least minor. The claim's Clause-②: no was conditional on exactly this ("If the dev must change an export … that is reported and the ACCEPT re-reads it"); the dev reported it, and this is the re-read: Clause-②: yes (widening) is owed, with @object-ui/components at minor. Neither gate on the head can catch this: Changeset Bump Policy enforces no-major only (check-changeset-no-major.mjs), Changeset Claim Re-read reads changeset prose against the diff, not the level axis. The changeset body already names the export; its frontmatter and the PR line do not.

What re-renders this to PASS: (a) PR body line 2 Clause-②: yes (widening); (b) the components changeset frontmatter '@object-ui/components': minor, its body stating the one new export as the widening (nothing removed or renamed, no producer required to use it); (c) plugin-grid unchanged at patch. No source, pin or dependency change is needed.

③ Boundary flags

  • open_questions: empty. None to answer.
  • Deviation 1 (commit trailers follow objectui AGENTS.md, not the harness reminder). Not a contract matter; the pair the repo rule asks for is what git log shows. Accepted.
  • Deviation 2 (push order: PR created at 18ebe6d47d, changesets pushed a minute later). The PR head is cb23049281 and the file list is the 9 reviewed. Accepted.
  • Deviation 3 (app-shell test-only, no changeset). Changeset Declaration success on the head. Accepted.
  • The claim's export condition. Reported as asked; the re-read lands in ② and is the one thing that fails this head.
  • Out-of-scope finding 1, plugin-grid's BulkActionBar — fileable as ONE card in the family; no open family card to put it on. Read at the head: bulkEligibility.ts:75–78 hasVisibilityGate is visible != null && visible !== '', BulkActionBar.tsx:48 hides on hasVisibilityGate(def) && eligible.length === 0, and core listConditional.ts:397 partitionRowsByPredicate opens with the same pred == null || pred === '', so a whitespace visible (either spelling) is evaluated per row, each row returns the fail-closed fallback, eligible is empty and the button is gone — while the same grid's row menu (this PR) and the related-list toolbar (PR objectui#11275) show the action. The dev measured it through the real BulkActionBar with one selected row: ' ' and { dialect: 'cel', source: ' ' } hidden, '' and ungated shown. The reach is a named authored key (BulkActionDefSchema.visible is ExpressionInputSchema; hand-written view JSON and in-process defs are the producers objectui#3492 recorded) with a measured verdict table, so it clears the seat's filing gate as class (a); the card should say the probe went through the component, not the registered grid node, so triage can ask for that one node-level probe if it wants it (the precedent is PR objectui#11275's ③ on DetailTabs). Among the threads I may read there is no open family card: objectui#3812 is closed, objectui#11244 and objectui#11270 are closed, objectui#3521 is closed, and the report's "that family's close-out card" is unnamed. The seat files one new card. Draft title: finding(plugin-grid,core): the selection bar hides a bulk action authored visible: ' ' that the same grid's rows and toolbars show — bulkEligibility hasVisibilityGate and core partitionRowsByPredicate keep their own "is a gate declared?" test (the objectui#3812 class). The card also carries why the objectui#3521 parity table has no whitespace row (predicate-surface-parity.test.tsx has none at the head).
  • Out-of-scope finding 2, built-in Edit / Delete visibleWhen and the record header. visibleWhen keeps != null on both row menus (data-table.tsx:264, RowActionMenu.tsx:226), and RecordDetailView.tsx:1422–1428 reads it through useRowPredicate(…, { fallback: false }), so all three hide on a blank: no surface disagrees, and it is a field-rule key under ADR-0137, not this family. Acceptance notes, as the dev placed it. Right.
  • Check-runs on the head, read 2026-10-01T03:12Z: 42 runs — 29 completed/success (among them Lint, Build & E2E, Build Docs, Test (dist pins), Changeset Fixed Group Check, Changeset Declaration, Changeset Bump Policy, Changeset Claim Re-read, Changeset Overwrite Report, README Export Check, Pre-Install Import Graph Check, Line Citation Gate, Control Byte Scan, Inert vi.mock Specifier Check, Doc Snippet Type Check, Bundle Analysis, Live E2E (informational)), 3 completed/skipped (Test (coverage) and its shard lane, push-only; dependabot), and 10 in_progress, not concluded: Test (shard 1/8) through Test (shard 8/8), Type Check, Spec Main Shape Gate. They were not waited on and are read as neither pass nor fail; the test and type gate families therefore have no conclusion on this head yet, and any re-render that follows ② starts a fresh set anyway.

Implemented-by: claude/issue-11294-row-menu-declared-gate
Reviewed-by: session_0122Knsowci76D2rBWReCzzZ

VERDICT: FAIL

…xport is a widening

`isCustomRowActionVisible` is new public surface of `@object-ui/components`:
the package's `exports` map publishes only `.` and `./style.css`, so the old
deep-path export was not public. plugin-grid stays patch.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3585.7 KB 3607.4 KB
Main entry chunk (gzip) 150.4 KB 350 KB
Entry file index-c7d3WjaY.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 571.63KB 136.90KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 229.96KB 63.80KB
fields (index.js) 261.05KB 66.23KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.95KB 11.48KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 52.90KB 15.35KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 139.34KB 37.30KB
plugin-designer (index.js) 216.45KB 44.60KB
plugin-detail (index.js) 244.79KB 64.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 173.70KB 44.63KB
plugin-gantt (index.js) 173.03KB 43.07KB
plugin-grid (index.js) 231.53KB 63.58KB
plugin-kanban (index.js) 49.32KB 15.48KB
plugin-list (index.js) 116.63KB 28.97KB
plugin-map (index.js) 23.54KB 7.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 33.05KB 9.67KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 21.59KB 7.71KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 57202704177d933302646e017caaabc536ebfe0c
Local-runs: none

A delta record. The prior record on this PR, 5924036373 on head cb23049281, was FAIL on ② alone; its ① and ③ were right and are carried here unchanged, because the delta git diff cb23049281 5720270417 touches no source, pin, dependency or export: one commit, 5720270417, whose parent is cb23049281 (no merge of main), changing .changeset/11294-components-row-menu-declared-gate.md alone, 5 insertions and 1 deletion. Inputs, and nothing else: that delta (both shas fetched by sha, diffed, read with git show; nothing checked out), the patch-round dev report 5924061730 on card objectui#11294, PR objectui#11319's body, /files (still 9 files, now +453 / −70, the +4 being this changeset) and /commits (3), and the head's check-runs, read twice. Nothing built, run or re-run; the dev's changeset:check and check-changeset-presence exit codes are quoted from the report, not re-derived. The PR is still behind main and mergeable.

① Derived judgments

Carried from 5924036373, items 1 through 8, verbatim in force: the one shared row helper with no third "declared?" test; the dependency direction; the disclosed accept-set move on the rows; the [blank] report through the family's channel; the red-first pins through the real block; the quoted ablation and reverse type check; the public-surface reading (ONE new named export, isCustomRowActionVisible(action, row, scope, fields?), on @object-ui/components' entry; nothing public leaves plugin-grid); and scope held. Re-checked at the new head where the delta could have moved them: packages/components/src/index.ts:70 still carries export { isCustomRowActionVisible } from './renderers/complex/data-table'; (present at cb23049281, absent at the merge-base caa0cd3927), the signature at data-table.tsx:300–304 is still (action, row, scope, fields?), and packages/components/package.json is byte-identical to the merge-base. Nothing in ① changes.

② Semver level

The delta is exactly the re-render ② asked for, and nothing more:

  • .changeset/11294-components-row-menu-declared-gate.md frontmatter '@object-ui/components': patch is now '@object-ui/components': minor — right. The diff publishes one new named export on a released package entry (① item 7); a widening takes at least minor.
  • The body keeps every prior line and adds one sentence: "Widening: isCustomRowActionVisible(action, row, scope, fields?) is a new public export of @object-ui/components; before this release it was reachable only by a deep module path that the package's exports map does not publish." — true. At the head the package's exports map has exactly two keys, . (types ./dist/index.d.ts, import ./dist/index.js, require ./dist/index.umd.cjs) and ./style.css; no ./renderers/* or wildcard subpath, so the module's own export function was shipped bytes, not published surface, until the barrel line. The sentence names the one export, states nothing removed or renamed, and asks no producer to adopt it, which is what the prior record required of the body.
  • .changeset/11294-plugin-grid-row-menu-declared-gate.md — unchanged, still '@object-ui/plugin-grid': patch, right. git diff cb23049281 5720270417 on that path is empty; its surface loses nothing. Both packages sit in the one fixed group of .changeset/config.json, so the group releases at minor whatever plugin-grid declares; the declaration axis is per package, what each diff publishes, and patch is plugin-grid's.
  • PR body line 2 reads Clause-②: yes (widening) — right. Line 1 Fixes #11294 is untouched.
  • The five Changeset checks on this head all concluded success: Changeset Declaration (2 changesets for 3 released packages, app-shell test-only), Changeset Bump Policy (no major), Changeset Claim Re-read (the changeset prose, now including the widening sentence, against the diff), Changeset Fixed Group Check, Changeset Overwrite Report.

The ② defect of 5924036373 is closed: the semver level matches what the diff publishes, and the Clause-②: line says so.

③ Boundary flags

Carried from 5924036373: deviations 1–3 accepted; the claim's export condition, now re-read in ② to its proper level; out-of-scope finding 1 (plugin-grid's BulkActionBar and core partitionRowsByPredicate keep their own "declared?" test — the seat files ONE new card, the draft title stands) and out-of-scope finding 2 (built-in visibleWhen and the record header, Acceptance notes, right). None of these moved: the bulk files, RelatedList.tsx and RelatedRecordActionsBridge.tsx are still not in the file list.

Patch round 1 (report 5924061730):

  • open_questions: empty. out_of_scope_findings: empty. None to answer.
  • Deviation (the body's Evidence line still says "The reruns are at head cb23049281"). An Acceptance note, not a defect: the reruns were run at cb23049281, the delta is a changeset file, and no source, pin or dependency moved, so the figures the line qualifies are the figures for this head's code. Left as written. Accepted.
  • "Nothing else in the body moved." The body's edit history is not readable from this session (the GraphQL route is refused; REST exposes no body-edit diff), so this rests on three readings that agree: the dev measured the body before and after at 9584 and 9596 characters, a difference of 12, which is exactly no (2) becoming yes (widening) (14); the body read now is 9596 characters and 74 lines, 9620 bytes in UTF-8 (the dev's figure is a character count); and every passage the prior record quoted or relied on — the export paragraph "for the ACCEPT to re-read", the seven-row verdict table, the [blank] report section, the Evidence block, both Acceptance notes, the one session footer — is present at its line, with the PR's single update falling between the prior record and the patch report. The relay's issue_patch neither doubled nor downgraded the footer, as the report says.
  • Push shape. The dev's "recreated from the branch at cb23049, committed, pushed with no merge of main" matches git log: 5720270417's sole parent is cb23049281; the PR's three commits are 18ebe6d47d, cb23049281, 5720270417.
  • Check-runs on the head, read twice, the second at 2026-10-01T03:24Z: 42 runs — 28 completed/success (among them the five Changeset checks above, README Export Check, Pre-Install Import Graph Check, Line Citation Gate, Control Byte Scan, Inert vi.mock Specifier Check, Doc Snippet Type Check, Bundle Analysis, Build & E2E, Build Docs, Test (dist pins), Live E2E (informational), Skill Example Check, Governed Surface Queue Guard), 3 completed/skipped (Test (coverage) and its shard lane, push-only; dependabot), and 11 in_progress, not concluded: Lint, Spec Main Shape Gate, Type Check, and Test (shard 1/8) through Test (shard 8/8). They were not waited on and are read as neither pass nor fail; the lint, type and test gate families have no conclusion on this head yet. Between the two reads README Export Check and Skill Example Check went from in_progress to success; nothing concluded other than success or skipped.

Implemented-by: claude/issue-11294-row-menu-declared-gate
Reviewed-by: session_0122Knsowci76D2rBWReCzzZ

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 1, 2026 03:35
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 1, 2026 03:35
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit be52115 Oct 1, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11294-row-menu-declared-gate branch October 1, 2026 03:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants