Repository navigation
fix(plugin-grid,plugin-detail,plugin-dashboard,plugin-list,fields): withhold an untyped column until the object field types are known (objectui#10657) - #10819
Conversation
…column while the object's field types are unknown (objectui#10657) An object-bound table whose rows paint before its object definition is read (ObjectGrid on the host-fetched path, RelatedList, ObjectDataTable) drew an untyped column over a password / secret field as text in that window, and for good when the read failed. Such a column is now WITHHELD: it draws the mask (MaskedCellRenderer, now exported from @object-ui/fields) and is flagged masked, and it stays withheld when the read fails. ListView hands the grid the field catalogue it already holds (objectFields, the ruled host channel), so a list view has no window at all. Claude-Session: https://claude.ai/code/session_01MCg3z5cGrV51xEddpGcxEY Co-authored-by: Claude <noreply@anthropic.com>
… withheld first paint and the host catalogue (objectui#10657) New pins: the grid's held / rejected / settled / host-catalogue arms with a typed text control, the group label, RelatedList's and ObjectDataTable's held / rejected / settled arms, and ListView's handoff of the catalogue with the rows. Existing in-flight pins wait for the mask instead of the raw value they used to print in that window. Claude-Session: https://claude.ai/code/session_01MCg3z5cGrV51xEddpGcxEY Co-authored-by: Claude <noreply@anthropic.com>
…be withheld columns; changesets (objectui#10657) The TableColumn.masked JSDoc, the data-table docs and four READMEs said an untyped column over a password / secret field still draws as text before the object's types are known; they now describe the withheld draw and the grid's objectFields prop. One changeset per touched published package. Three more existing pins read an untyped cell's raw value inside that window and now read the mask or wait for the settled draw. Claude-Session: https://claude.ai/code/session_01MCg3z5cGrV51xEddpGcxEY Co-authored-by: Claude <noreply@anthropic.com>
…compiled snippet (objectui#10657) Claude-Session: https://claude.ai/code/session_01MCg3z5cGrV51xEddpGcxEY Co-authored-by: Claude <noreply@anthropic.com>
…er values while types are unknown (objectui#10657) The same window, one click further: ObjectGrid's record panel (overlay navigation, no declared fields) and ObjectDataTable's record drill-down drawer drew a clicked row's fields from the unknown types, so after a failed read they printed a password / secret field as text. Both now draw every value as the mask in that window. Pinned beside the column arms. Claude-Session: https://claude.ai/code/session_01MCg3z5cGrV51xEddpGcxEY Co-authored-by: Claude <noreply@anthropic.com>
…des (objectui#10657) Claude-Session: https://claude.ai/code/session_01MCg3z5cGrV51xEddpGcxEY Co-authored-by: Claude <noreply@anthropic.com>
|
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: 246/246 Read: card objectui#10657 (body; comments ① Derived judgmentsA. Fail closed: is an object-bound untyped column's raw value absent from the DOM while types are pending AND after a failed read?
B. The grouping refusal for a withheld key. C. D. The E. The F. The eight edited pins — signal only, or weakened?
G. The four new pins. H. Prose, sentence by sentence, at the head. The six changesets: TRUE throughout, including the fields one's ② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL |
…he schema window (objectui#10657) `rowRecordCrudVerdict`'s 250-row case (CI `Test (shard 3/8)`) waited for the raw text of an untyped column. Since this card that column is withheld (drawn as the mask) until the grid's own schema read lands, so the wait covered a re-render of every row and outran waitFor's budget. Measured by a runtime sweep over the 264 test files that render these producers with a schema read: five cases wait across a withheld window on 50 or more rows. Their readiness now counts the rows on screen (rowRecordCrudVerdict x2, serverPagination x2), or, for the ListView paging shape, hands the grid the field catalogue that host now passes (groupedPartialDisclosure-7189). No assertion of the tests' subjects changes; no timeout is raised. Claude-Session: https://claude.ai/code/session_01MCg3z5cGrV51xEddpGcxEY Co-authored-by: Claude <noreply@anthropic.com>
✅ 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: 108/108 Read: the previous at-tier record on ① Derived judgmentsThe FAIL defect — FIXED at its root. The old head's one defect was (a) The five cases, readiness before → after, first-paint truth, and what each still gates.
Across the five: no assertion of any subject changed (call counts, cap chunking, asked ids, notice text (b) The fixture edit in (c) The class boundary. The dev fixed every case whose withheld window covers 50 or more rows and left the ≤9-row cases (155, per the deviation list). Tested against the evidence rather than trusted: within the three files, the cases still waiting on a withheld cell's raw text inside the window are the 13 two-row cases of (d) Every file not in the delta. ② Semver levelUnchanged from the previous record, on byte-identical package sources: ③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #10657
Clause-②: yes
PR 2 of 2 for objectui#10657, the masked-column closure card. PR 1 (objectui#10725, landed as
9c78ebe) made the three producers setTableColumn.masked. This PR carries the two rows the release comment5852410452left: the first paint before the object schema settles (the folded objectui#10706 row), and the same draw window atRelatedListandObjectDataTable.Branch
claude/issue-10657-masked-first-paint, base objectuiorigin/mainc907a9c87, head9f9b8b7. Implemented in sessionhttps://claude.ai/code/session_01MCg3z5cGrV51xEddpGcxEYfor the PM claim5856905742.What changed
Withheld columns (the triage's "for every other case"). While an object-bound producer's field types are unknown (its definition read is in flight, or it failed), a column it cannot type is drawn as the mask (
MaskedCellRenderer) and carriesmasked. It stays so when the read fails: fail closed, never back to text.ObjectGrid(@object-ui/plugin-grid).objectTypesPendingis true when there is an object name, the data source can answergetObjectSchema, and no field catalogue is in hand. An untyped column (no authoredtype) on any of the three column paths (authoredListColumns, bare field names, host row keys) is withheld. No heuristic type is inferred for it, and its cell is the mask.isMaskedGridColumntakes a third, required argument, so the masked flag, the grid's client export (CSV and JSON), the mobile card and the grouping refusal all treat a withheld column as masked. A column that authors its owntypedraws from it, as before.RelatedList(@object-ui/plugin-detail). This list draws cells from the object's types only (makeCell), never from an authoredtype. So in its window (objectTypesPending, from PR 1), every column without acellof its own gets the mask as itscell, at the one seam into the table (tableColumns). An author-suppliedcelldraws what it returns.ObjectDataTable(@object-ui/plugin-dashboard). In its window, a column with no authoredtypegets the mask as itscell, and a column with an authoredtypedraws from it. Its record drill-down drawer (RecordDetailDrawer, internal) takes the table'sobjectTypesPendingand draws every value as the mask in that window.No window where the host already holds the types (the triage's first shape).
ListView(@object-ui/plugin-list) reads the object definition before it fetches the rows (the data fetch waits forobjectDefLoaded). It now hands the definition'sfieldsto the grid as theobjectFieldsReact prop, besidedata. The grid answers field-type questions from that catalogue until its own read lands. Only the grid view is handed it.The mask's owner exports it.
@object-ui/fieldsnow exportsMaskedCellRenderer, withfieldoptional. The three producers draw a withheld cell with the one mask the fields package owns and do not restate the glyph. That is the reason PR 1's review gave for keeping the mask's drawing out ofcomponents.Mechanism assumptions (Zone 2), measured
c907a9c87.objectSchemastartsnull, and untyped columns take their type from it or frominferColumnType.catchswallows a failedgetObjectSchema("Schema fetch failure for inline data is non-fatal").objectTypesPendingstamps every column in both producers, and neither producer attached a cell in that window.RelatedList'smakeCellneeds a definition, andObjectDataTable'srenderFieldValuegot no type. So the table drew the value.ObjectGrid.tsxhunks was checked, context by context, against this branch's base and head. Eight match on both.-2349hunk already misses onorigin/mainc907a9c87, before this branch: main moved under that draft.plugin-gridREADME hunk matches on both.objectFieldsonObjectGridComponentProps.SchemaRendereralready strips an AUTHOREDobjectFieldsin all three spellings (objectui#8818), so no view author can hand the grid a catalogue that types a credential field as text.objectSchema, would have been spread from authored metadata straight into the grid.Clause-②: yesholds: this adds a new optional prop to a published component's props type, and a new export to@object-ui/fields.Design choices, stated
RelatedListandObjectDataTablealready paid PR 1's stated cost in that window (no copy, tooltip, export or sort). Now the cells are masked too.Beyond the claimed file surface (stated, not silent)
packages/fields/src/index.tsx,packages/fields/README.mdand aminorchangeset: theMaskedCellRendererexport, from the mask's owner.packages/plugin-dashboard/src/RecordDetailDrawer.tsx, and the grid's record panel insideObjectGrid.tsx, outside the column-type region.packages/types/src/data-display.ts(JSDoc only),content/docs/components/complex/data-table.mdx, and the grid, detail and dashboard READMEs. Their text said an untyped column is still drawn as text in this window, and this change makes that false.authoredColumnsFls-6799,inlineDataFls-6723: the in-flight case waits for the mask.hostFetchedDefaultColumns-6677: the two in-flight cases wait for the mask.RelatedList.maskedColumn-10657: the settled wait also requires the drawn name, and the two fail-closed cases wait for the mask.ObjectDataTable.maskedColumn-10657: the fail-closed fixture'sNamecolumn authorstype: 'text', so it still draws. That file pins the flag, which still stamps every column.ObjectDataTable.fetchGate-10664: the thrown-read case waits for the mask.RelatedList.multiValueParentScope-7299: the thrown-read case counts the one parent-scoped row by its mask.RecordRelatedListRenderer.unresolvedIdentityFailClosed-8793: the census case waits for the settled draw.object-data-table, types) describe the window as uncovered or as drawing text. The overwrite gate forbids editing them, so each new changeset says which of those sentences no longer holds.Pins (new)
plugin-grid,maskedFirstPaint-10657(8 cases). Held, rejected, settled, host catalogue, group label (held and rejected), and the record panel. Each case hasNamebeside it, a column that authorstype: 'text'and so draws, tooltips and copies in every arm.plugin-detail,RelatedList.maskedFirstPaint-10657(6 cases). Held, rejected and settled, over bare-name and authored-object columns, beside an author-suppliedcellcolumn.plugin-dashboard,ObjectDataTable.maskedFirstPaint-10657(6 cases). Held, rejected (inline and fetched rows), auto-derived, settled, and the record drawer, beside a column that authorstype: 'text'.plugin-list,ListView.gridObjectFields-10657(2 cases). Every props bag that hands the grid rows also carries the declared fields. The control: after a failed definition read, the rows arrive with no catalogue.Ablation. One run, restored by a trap.
c907a9c87. The fix markers (objectTypesPending,withheldCell, theobjectFieldshandoff, theMaskedCellRendererexport) read 0 on disk, and every blob equalled its base blob.Test Files 4 failed (4)andTests 20 failed | 1 passed (21). The one pass is the ListView control arm.HEAD:git diff HEADwas empty, every blob equalled itsHEADblob, and the same run gaveTests 21 passed (21).Verification
All runs went through the shared verify lock. Head-pinned;
9f9b8b7differs frome8503d3by one changeset file only.e8503d3.plugin-grid:Test Files 163 passed (163),Tests 1525 passed (1525).plugin-dashboard:Test Files 138 passed (138),Tests 1290 passed (1290).plugin-detail:Test Files 215 passed | 1 skipped (216),Tests 2108 passed | 8 skipped (2116).e742e22. The sources of these two packages are unchanged since the first commit.plugin-list:Test Files 103 passed (103),Tests 1171 passed (1171).fields:Test Files 214 passed | 1 skipped (215),Tests 3453 passed | 7 skipped (3460).e742e22. Every test file outside the four producer packages (and outsidetypes,core,fields,components,scripts) that names these components andgetObjectSchema:Test Files 96 passed (96),Tests 1680 passed (1680).e8503d3.turbo run type-checkforfields,types,plugin-grid,plugin-list,plugin-detailandplugin-dashboard:Tasks: 21 successful, 21 total, with each package'stype-checkscript echoed and noerror TS. Each package'stsconfig.test.jsonlists its new pin (--listFiles, 1 hit x4).e8503d3. eslint on the 20 changed TS files gave 0 errors.ListView.tsx, which goes from 184 to 185 (react-hooks/refs). The new read ofviewComponentSchema.typecarries the same finding that the existingschema={viewComponentSchema}read already carries. Warnings do not gate here.eslint.config.jssets noparserOptions.projectorprojectService, so linting is not type-aware; the population is the changed files from the diff againstc907a9c87; the count of 20 is read from the JSON output.9f9b8b7ore8503d3.check-changeset-presence: "20 source file(s) of 6 released package(s) changed, and this change declares 6 changeset(s)".check-changeset-no-major,check-changeset-fixed.check-changeset-overwrite: "No pre-existing changeset was modified or deleted".check:pending-changeset-literals,check:control-bytes.check:new-line-citations: "0 new citation(s)".check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape.check-doc-links: "Links are valid across 17 scan roots".check:doc-fences,check:doc-types,check:unreferenced-sources,check:phantom-deps.check:changeset-claims(report-only).check-governed-queue-guard --teston the 31 paths: "NOT GOVERNED".check:doc-snippets: it exited 2 with PREREQUISITE NOT MET, because its 34-package build did not run here; CI builds it. For that reason the grid README states the new prop in prose rather than in an uncompiled snippet.check:readme-exports: it exited 1 with "self-import(s) could not be judged" for unbuilt packages. The newfieldsREADME import resolves against the builtfieldsdist.Acceptance notes (not in this PR)
RelatedList's own opt-in filter box and itslistsort buttons read raw values (objectui#10728, ordered after this card).data-tableheader context menu ignoressortable: false.RelatedList's other bodies are not the data-table seam and were not measured for this window: the mobileobject-gallerycard view andtype: 'list'(data-list).prefixcompound cell on the grid draws its prefix field as a badge or as text whatever that field's type. This is an authored presentation, the same boundary as an authoredtype: 'text'over asecretfield, in the settled state too.type: 'text'over asecretfield draws its text in the window, as it does once settled. Its flag in the grid's window follows the authored type until the object's type is known.Generated by Claude Code