Repository navigation
fix(components,plugin-grid): the row menu asks the action family whether a visible gate is declared (objectui#11294) - #11319
Conversation
…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
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs, and nothing else: card objectui#11294 (body; triage ① Derived judgments
② Semver levelChangesets:
What re-renders this to PASS: (a) PR body line 2 ③ Boundary flags
Implemented-by: 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
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: A delta record. The prior record on this PR, ① Derived judgmentsCarried from ② Semver levelThe delta is exactly the re-render ② asked for, and nothing more:
The ② defect of ③ Boundary flagsCarried from Patch round 1 (report
Implemented-by: VERDICT: PASS |
Fixes #11294
Clause-②: yes (widening)
What changed
A related list's row menu now gives the same answer as its toolbar to "is a
visiblegate declared?". An action authoredvisible: ' 'shows on the toolbar and on the rows, whether the rows reach it throughrecord_relatedorlist_item.@object-ui/components:isCustomRowActionVisible(the data table's row menu) askshasDeclaredVisibilityGate, the action family's one definition, instead of its ownpred == null || pred === ''. A blankvisible('', whitespace, or an envelope whosesourceis 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 tohasDeclaredVisibilityGate.@object-ui/plugin-grid:RowActionMenu's twin ofisCustomRowActionVisibleis 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).Export change, for the ACCEPT to re-read (as the claim asked):
@object-ui/componentsgains 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)
hasDeclaredVisibilityGatelives in@object-ui/components(renderers/action/visibility-gate.ts) as a re-export of core'shasDeclaredPredicate. Blank text, whitespace, a blank-sourceenvelope and junk are "not declared". It reports each blank once with the context "a declared gate, read as no gate".isCustomRowActionVisiblebodies returnedtrueonly forpred == null || pred === ''. Anything else went toevalRowPredicatewithfallback: false, so a blank failed closed. The two bodies differed only in plugin-grid's extrafieldsargument.Old and new row verdicts, measured with a throwaway probe (deleted), row
{ x: 1 }:visible' '''{ dialect: 'cel', source: '' }{ dialect: 'cel', source: ' ' }0,{},[](not a predicate)falsetrue,'record.x == 1'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 realRelatedRecordActionsBridge, therecord:related_listnode through the realSchemaRenderer, plugin-detail's registration, the realRelatedListand the real data table, at desktop width. The toolbar showsblank_rr,blank_li,empty_rrandempty_li. The opened row menu shows the same four beside the ungatedsend_remindercontrol.never_li(visible: false) stays hidden on both. CEL gates are evaluated against the row:cel_yes_rrshows andcel_no_rrdoes not. A rows-only blank is reported exactly once.packages/components/.../data-table-row-action-visible.test.tsx: the item and theplanDataTableRowMenuguard, for whitespace, a whitespace-sourceenvelope and an empty-sourceenvelope.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.tsxnow 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 headcb23049281.caa0cd3927, the new real-block pin failed withexpected [ 'send_reminder', 'empty_rr', …(2) ] to deeply equal [ 'send_reminder', 'blank_rr', …(4) ]:blank_rrandblank_liwere absent from the row menu. The toolbar assertion passed. This reproduces the card's table on current main.ablation-replace.mjs, wrap mode): the shared helper'sif (!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/componentsalone, which shows that plugin-grid reads the one function. Thenok restored: blob == HEAD (de5ae999a55b) and git diff HEAD is empty.tscfail witherror TS2554: Expected 3-4 arguments, but got 5., so plugin-grid reads the rebuilt.d.ts. Restored, blob == HEAD.pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-grid^...' buildexited 0.--filter '@object-ui/app-shell^...' buildexited 0.@object-ui/components,@object-ui/plugin-grid,@object-ui/plugin-detailand@object-ui/app-shelleach rantsc --noEmit && tsc -p tsconfig.test.jsonwith exit 0.--listFilesshows that each test program reads the touched test files.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).git grep -lfinds forisCustomRowActionVisibleorhasDeclaredVisibilityGate, plus the objectui#11270 bridge suite:Test Files 13 passed (13),Tests 262 passed (262).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: nomajor.check:control-bytes: OK.check:changeset-claims,check:pending-changeset-literals,check:test-path-rootsandcheck:action-forward-parityalso passed.Lintjob owns the full run. Hereeslint --format jsonran on the 7 touched.ts/.tsxfiles:files 7 errors 0 warnings 67. ① The population was read from eslint's own config:eslint --print-configresolves a config for each of the 7, so none is ignored. ② The file count, 7, comes from the JSON output. ③ Invariance:eslint.config.jsenables no type-aware linting (noprojectService, noparserOptions.project), and no rule ineslint-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: +1no-explicit-anyin the new guard pin, −2 inRowActionMenu.tsxwith the twin removed.Acceptance notes
!= null && !== '': plugin-grid'sbulkEligibilityhasVisibilityGate, with core'spartitionRowsByPredicateopening the same way. Measured through the realBulkActionBarwith one selected row:visible: ' 'and a whitespace-sourceenvelope hide the bulk button, while''and an ungated def show it. The row kebab now shows all four. So for a whitespacevisible, 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.visibleWhenkeeps its!= nullrule on both row menus. Read at source and not measured: the record header's Edit/Delete (RecordDetailView,useRowPredicatewithfallback: 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