Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-list): a list-view with no authored columns renders the grid's defaults, not an empty table - #6679
Merged
Conversation
…jection
A production `kind:'html'` page carried `<list-view objectName="opportunity">`
with no `columns` and rendered the row count, the filter/group/sort toolbar and
the index column — and not one data column, with no diagnostic anywhere.
`ObjectGrid` derives default columns for exactly that case ("Default columns
priority (when schema doesn't specify columns)"), and it never ran: the
derivation is gated on `schema.fields` being ABSENT, `ListView` sent `fields:
[]`, and an empty array is truthy. `normalizeColumns` had already read the empty
`columns` as unauthored, so the two keys disagreed about one fact and the
stricter reading won.
`ListView` now asks whether the AUTHOR declared a projection — `columns` present
and non-empty, after the legacy `fields` fold — and hands the child grid nothing
when they did not. The predicate reads the authored value and never what
survived filtering: when the author DID declare columns and the field gate
removed every one, the empty projection is still sent, because `ObjectGrid`
re-applies FLS on its derived path only.
Measured single-variable on the html tier: a bare `<object-grid objectName=…/>`
renders the object's default columns; the same object behind `<list-view>`
rendered none. Pinned at the handoff and end to end over the real grid.
Part of objectui#6598.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49`SchemaRenderer` hands a registered component its schema and also spreads the schema's keys as props; `ObjectGrid` reads the `schema` one. The handoff pin was asserting the spread copy — the same value today, and the copy that would keep agreeing if the two ever diverged. It now reads `props.schema`, with a truthiness assertion on the accessor itself so a renamed prop cannot turn every `toBeUndefined()` into a pass over nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Contributor
✅ 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
|
os-sales
marked this pull request as ready for review
August 28, 2026 14:05
Uh oh!
There was an error while loading. Please reload this page.
os-sales pushed a commit
that referenced
this pull request
Aug 29, 2026
…nd:'html' page The reported defect no longer reproduces on main. Re-measured every one of the eight spellings objectstack#12649 tried: none lands in the reported state (a populated table whose only header is the index column, with zero diagnostics). Six render data columns — forms 1 and 3 render exactly the authored projection, forms 2, 4, 5, 7 and 8b render the block's defaults — and forms 6 and 8a fail loudly with the compile-error block on screen. Three separate mechanisms produced one symptom, and each was fixed elsewhere: the braced literal subset (#6614 / PR #6669), the unauthored-projection handoff (PR #6679), and the grid's default-column derivation (#6677). What none of those pinned is the card's own claim, which is a statement about all eight spellings at once. This adds that matrix, through the real page renderer, the real html-tier compile against the real registry manifest, the real `list-view` registration and the real `object-grid` — so no future change can put any spelling back into the reported state. Tests only; the changeset declares an empty frontmatter accordingly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #6598
The card was dispatched as a re-measurement: PR #6669 had landed objectui#6614's Q1-A an hour earlier, and the standing rule is that the merge discharging a blocker is the one most likely to have already discharged the downstream. It had — for two of the eight reported spellings. This PR carries the measurement and fixes the one remaining form that failed with no diagnostic at all.
The eight reported forms, re-measured on the merged ref
All measured through the live path on
origin/main@ece68882: registry-built manifest (the exactgetJsxManifestconstruction incomponents/src/renderers/layout/page.tsx), the real html-kind compile,SchemaRenderer, the realplugin-listregistration, and — for the column counts — the realobject-grid, counting rendered header cells. Objectopportunity, fieldsid (hidden)/name/stage/amount/close_date.columns={["name","amount"]}(strict JSON)columns={['name','amount']}columns='["name","amount"]'(JSON string)warning type-mismatchcolumns={[{field:'name',label:'Name'}, …]}viewName="all"warning unknown-propview="all"warning unknown-propcolumnelementserrorcolumnsat allobject-name="opportunity"(kebab)error missing-required-propview-name="all"(kebab)warning unknown-propForms 1 and 3 are exactly the grammar cases #6669 legalised, and they are not re-implemented here — the credit is entirely #6669's. Forms 2, 6 and 8a fail loudly and arguably correctly: a JSON string where an array is declared is a type mismatch (accepting it would be a consumer-side tolerance alias), and the other two are page-fatal compile errors the author cannot miss.
Form 7 is the one that was silent, and it is the one an AI drafts first.
Form 7: the mechanism, and the single-variable control that isolated it
ListViewcomputeseffectiveFieldsand hands it to the child grid as bothfieldsandcolumns. When the author declared no columns that value is[]— and an empty array is truthy, soObjectGrid'sif (schemaFields) fieldsToShow = schemaFields;pins the projection at zero and neither of its default-column derivations can run.normalizeColumnshad already read the emptycolumnsas unauthored, so the two keys disagreed about one fact and the stricter reading won.The control, same tier, same page kind, same data source, same object — only whether
list-viewsits in between:object-gridwithobjectName, no columnslist-viewwithobjectName, no columnsThat is the reporter's "no data columns at all — only the index column", reproduced on the merged ref.
The change
ListViewnow asks whether the author declared a projection —columnspresent and non-empty, read after the legacyfieldsfold — and hands the grid nothing at all when they did not, so the grid's own defaults apply.effectiveFieldsis also[]when the author DID declare columns and the field gate removed every one of them; that case must keep sending the empty projection, becauseObjectGridre-applies FLS on its derived column path only, never on the explicit-columns path — treating it as "unauthored" would put fields on screen past the field gate. It is pinned as its own case.⛔ What this does NOT do, stated so it is not over-read
It restores the grid's defaults; it does not decide which defaults. With a host owning the fetch,
ObjectGridtakes its inline-data branch and derives from the row payload's keys, so a barelist-viewnow shows every fetched field — measured 10 columns includingIdandCreated By, where the same object with the grid owning the fetch shows 5 under the object-schema policy (hidden dropped, readonly system-managed dropped,highlightFieldshonoured). That precedence lives inpackages/plugin-grid, which was fenced for this dispatch, and is filed as #6677.So: usable table instead of an unusable one, and the noisy-vs-policy question is #6677's.⚠️ Sequencing is a maintainer call — this can land alone (a noisy table is self-correcting for an author; an empty one reads as a broken platform) or wait for #6677.
Forms 4, 5 and 8b are also restated rather than fixed. The spec's
dataSource={{object:'opportunity', view:'all'}}does resolve a saved view on this tier — measured, columns arrive — but it drawsunknown-propbecause no registration declares it and it is not a base prop, so the tier reported the working spelling exactly like the two broken ones. Filed as #6678; it needs a declaration-surface ruling, not a patch.A suspected FLS bypass on the SDUI path was disproven by measurement, not assumed away: with
amountdenied andcolumns={['name','amount']}authored, both the html-tier path and the direct host path drop the column from the headers and from$select. No finding filed.Tests
Two pins, because the mechanism and the symptom are at different layers:
ListView.unauthoredColumnProjection-6598.test.tsx— the handoff, against a stubobject-grid(the idiomListView.findParamsHandoff.test.tsxset; plugin-list does not depend on plugin-grid). Four cases: unauthored, empty-columns-is-unauthored, the authored positive control, and the FLS case that must stay explicitly empty. Assertions readprops.schema, the copyObjectGridactually reads, with a truthiness check on the accessor so a renamed prop cannot turntoBeUndefined()into a pass over nothing.htmlTierListViewDefaultColumns-6598.test.tsx— the reported page end to end over the real grid, counting header cells. Registered inheavyDomTestsfor the setup's plugin-grid registration, the routeListView.crossPageSelectAll.test.tsxtakes. A stub cannot see this defect: it is two components disagreeing, and the stub stands in for one of them.Reverse verification
From the committed state,
ListView.tsxreverted to baseece68882and the mutation confirmed on disk (HEADblob9e68c598…→f5245657…,hasAuthoredColumnshits 4 → 0):3 red, 3 green — the authored control, the FLS case and the authored end-to-end case all hold at base, so the pins are not red for a general reason. Restored with
git checkout HEAD -- PATH;git diff HEADempty, blob back to9e68c598…, marker hits back to 4.Gates
Union run at final HEAD
69447464, each verdict line as the gate printed it, exit status captured before any pipe:pnpm exec vitest run packages/plugin-list/Test Files 51 passed (51)·Tests 702 passed (702)pnpm --filter @object-ui/plugin-list run type-checktsc --noEmit && tsc -p tsconfig.test.json)pnpm check:control-bytes✅ check-control-bytes: OK (scanned 5519 tracked text file(s); skipped 85 binary).pnpm --filter @object-ui/plugin-list lint✖ 428 problems (0 errors, 428 warnings)pnpm lint:root(coversvitest.config.mts)✖ 28 problems (0 errors, 28 warnings)pnpm check:phantom-deps✅ Every in-scope import is declared by the package that publishes it.vitest run scripts/__tests__/vitest-invocation-guard.test.tsTests 30 passed (30)The four gate runs were chained with
&&so the status certifies all of them, under the shared verify lock (VERDICT command-exit 0)."Type-check is clean" is measured, not assumed:
tsc -p tsconfig.test.json --listFilesputs both new test files andListView.tsxinside the closure (1 hit each), so the green covers them.Lint scope:
turbo run lintruns oneeslint .unit per package plus the root unit. Both units containing a changed file were run whole — no within-unit narrowing — and no other package's unit reads a file this diff touches. Changed files carry 0 errors:ListView.tsx0/177 warnings (pre-existingno-explicit-anyin a 3.8k-line file; the added lines contribute none), the two new tests 0/4 and 0/2 (no-explicit-anyon the stub props, matching the sibling tests),vitest.config.mts0/1.vitest-invocation-guard.test.tsis run because this diff editsvitest.config.mts— deriving the gate families from the changed paths does not answer "what tests test the thing I changed".Notes
@object-ui/plugin-listpatch.list-view, the live registration does declarecolumns, and the declared-inputs hypothesis could not have produced the symptom). Nothing here implements from them. Re-titling is triage's write.packages/plugin-grid,packages/types,packages/components/src/renderers/complex/data-table.tsx, and the objectstack half of thesdui-parserlockstep.Generated by Claude Code